Oxpecker 0.13.1

dotnet add package Oxpecker --version 0.13.1                
NuGet\Install-Package Oxpecker -Version 0.13.1                
此命令旨在在 Visual Studio 的包管理器控制台中使用,因为它使用 NuGet 模块的 Install-Package 版本。
<PackageReference Include="Oxpecker" Version="0.13.1" />                
对于支持 PackageReference 的项目,请将此 XML 节点复制到项目文件中以引用包。
paket add Oxpecker --version 0.13.1                
#r "nuget: Oxpecker, 0.13.1"                
#r 指令可用于 F# Interactive 和 Polyglot Notebooks。将此内容复制到交互式工具或脚本的源代码中以引用包。
// Install Oxpecker as a Cake Addin
#addin nuget:?package=Oxpecker&version=0.13.1

// Install Oxpecker as a Cake Tool
#tool nuget:?package=Oxpecker&version=0.13.1                

Oxpecker

Oxpecker 是一个基于 ASP.NET Core 端点路由的 F# 框架(类似于 Minimal APIs,因此它们是竞争对手),具有易于理解的 API,主要继承了 Giraffe 框架。

Nuget 包 dotnet add package Oxpecker

示例可以在这里找到 这里

性能测试位于 这里

文档

对 Oxpecker 所有功能的深入功能参考。

目录

基本概念

核心概念

Oxpecker 是基于 ASP.NET Core 端点路由 构建而成,并为 F# 用户提供了方便的声明式语言。

使用 Oxpecker 时,请确保您熟悉 ASP.NET Core 以及其概念,因为 Oxpecker 会重用很多内置功能。

EndpointHandler

Oxpecker 中的主要构建块是 EndpointHandler

type EndpointHandler = HttpContext -> Task

EndpointHandler是一个函数,它接受HttpContext,并在完成后返回一个Task

EndpointHandler函数完全控制传入的HttpRequest和生成的HttpResponse。它紧密遵循RequestDelegate签名,但采用F#风格。

EndpointHandler通常应被视为一个终端处理器,这意味着它应该向响应写入一些结果(但不一定,如组合部分所述)。

EndpointMiddleware
type EndpointMiddleware = EndpointHandler -> HttpContext -> Task

EndpointMiddlewareEndpointHandler类似,但接受第一个参数为next EndpointHandler

每个EndpointMiddleware都可以在通过调用下一个EndpointMiddleware继续向下传递到Oxpecker管道之前处理传入的HttpRequest,或者通过返回自己的Task来提前中断执行。

EndpointHandler 与 EndpointMiddleware 的对比

那么,何时你应该定义一个或另一个呢?答案在于处理器的职责

  • 如果你想要有条件地返回响应或进一步在管道中执行,请使用EndpointMiddleware。示例是认证端点中间件预条件端点中间件
  • 如果你想在下一个处理器完成后执行某些逻辑,请使用EndpointMiddleware
  • 其他情况下请使用EndpointHandler

Oxpecker 管道与 ASP.NET Core 管道的比较

Oxpecker 管道是与 ASP.NET Core 管道(面向对象)的(类似)函数等价物。ASP.NET Core 管道由中间件定义,而EndpointMiddleware类似于常规中间件,EndpointHandler类似于终端中间件。

如果 Oxpecker 管道没有处理传入的HttpRequest(因为没有匹配路由),则其他 ASP.NET Core 中间件仍然可以处理请求(例如,静态文件中间件或在 Oxpecker 之后插入的另一个 Web 框架)。

这种架构允许 F# 开发者通过函数组合构建丰富的前端应用程序EndpointMiddlewareEndpointHandler函数,同时通过使用现有的 ASP.NET Core 中间件而从更广泛的 ASP.NET Core 生态系统受益。

Oxpecker 管道通过OxpeckerMiddleware本身集成到更广泛的 ASP.NET Core 管道中,因此是对它的补充而不是替代。

创建新的 EndpointHandler 和 EndpointMiddleware 的方法

有几种方法可以在 Oxpecker 中创建一个新的EndpointHandler

最简单的方法是重用现有的EndpointHandler函数

let sayHelloWorld : EndpointHandler = text "Hello World, from Oxpecker"

你还可以在返回现有的EndpointHandler函数之前添加附加参数

let sayHelloWorld (name: string) : EndpointHandler =
    let greeting = sprintf "Hello World, from %s" name
    text greeting

如果你需要访问HttpContext对象,你必须显式地返回一个接受HttpContext对象并返回TaskEndpointHandler函数

let sayHelloWorld : EndpointHandler =
    fun (ctx: HttpContext) ->
        let name =
            ctx.TryGetQueryValue "name"
            |> Option.defaultValue "Oxpecker"
        let greeting = sprintf "Hello World, from %s" name
        text greeting ctx

定义新EndpointHandler函数的最啰嗦版本是显式返回一个Task。在EndpointHandler函数内调用异步操作时非常有用。

type Person = { Name : string }

let sayHelloWorld : EndpointHandler =
    fun (ctx: HttpContext) ->
        task {
            let! person = ctx.BindJson<Person>()
            let greeting = sprintf "Hello World, from %s" person.Name
            return! text greeting ctx
        }

EndpointMiddleware的构建方式与EndpointHandler非常相似,但它接受一个额外的EndpointHandler作为第一个参数

let tryCatchMW : EndpointMiddleware =
    fun (next: EndpointHandler) (ctx: HttpContext) ->
        task {
            try
                return! next ctx
            with
            | ex ->
                ctx.Response.StatusCode <- 500
                return! text (sprintf "An error occurred: %s" ex.Message) ctx
        }
推迟 Task 的执行

请务必注意,在.NET中,一个Task<'T>表示任务最终异步完成时对类型的承诺。除非您以最冗长的方式定义一个EndpointHandler函数(带有task {} CE)并显式使用let!return!等待嵌套结果,否则处理程序不会在返回到OxpeckerMiddleware之前等待任务完成。

如果您想在任务返回后执行EndpointHandler中的代码,如使用use关键字清理资源,这具有重要的意义。例如,下面代码中的IDisposable将在返回实际响应之前被释放。这是因为EndpointHandlerHttpContext -> Task类型,因此代码text "Hello" ctx只返回了一个尚未完成的Task

let doSomething : EndpointHandler =
    fun ctx ->
        use __ = somethingToBeDisposedAtTheEndOfTheRequest
        text "Hello" ctx

但是,通过在task {} CE中显式调用text,可以确保在释放IDisposable之前执行text

let doSomething : EndpointHandler =
    fun (ctx: HttpContext) ->
        task {
            use __ = somethingToBeDisposedAtTheEndOfTheRequest
            return! text "Hello" ctx
        }

组合

处理程序组合

鱼形操作符(>=>)将两个函数组合成一个。

它可以组合

  • EndpointMiddlewareEndpointMiddleware
  • EndpointMiddlewareEndpointHandler
  • EndpointHandlerEndpointHandler

这是Oxpecker中的一个重要组合器,它允许将许多较小的函数组合成一个大型的Web应用程序

鱼形操作符可以连接的函数数量没有任何限制

let app =
    route "/" (
        setHttpHeader "X-Foo" "Bar"
        >=> setStatusCode 200
        >=> text "Hello World"
    )

每个函数都可以决定:短路管道或继续。对于EndpointMiddleware,它是选择调用next或不调用,对于EndpointHandler,它是开始编写响应或不编写。

如果您想了解更多有关>=>(鱼形)操作符的起源,请参阅Scott Wlaschin的技术博客文章关于铁路导向编程

routef函数不能直接与鱼形操作符一起使用,因此添加了额外的操作符以使路由可读性更高

routef "/{%s}" (setStatusCode 200 >>=> handler)
routef "/{%s}/{%s}" (setStatusCode 200 >>=>+ handler)
routef "/{%s}/{%s}/{%s}" (setStatusCode 200 >>=>++ handler)
绑定组合

bindQuerybindFormbindJson辅助函数可以被处理程序组合

route "/test" (bindQuery handler)

routef在与bind*函数组合时也需要额外的操作符

routef "/{%s}" (bindQuery << handler)
routef "/{%s}/{%s}" (bindForm <<+ handler)
routef "/{%s}/{%s}/{%s}" (bindJson <<++ handler)
多路由处理程序

有时,您希望使用一些通用的处理程序或中间件不仅仅是与一个路由一起使用,而是与整个路由集合一起使用。这可以通过使用applyBeforeapplyAfter函数来实现。例如


let MY_HEADER = applyBefore (setHttpHeader "my" "header")

let webApp = [
    MY_HEADER <| subRoute "/auth" [
        route "/open" handler1
        route "/closed" handler2
    ]
]

Continue 与 Return

在Oxpecker中,特定的EndpointMiddlewareEndpointHandler可以使用两种场景

  • 与下一个处理程序继续
  • 提前返回
继续

例如,一个假设的中间件,它在设置给定的HTTP头之后,总是调用到next http处理器

let setHttpHeader key value : EndpointMiddleware =
    fun (next: HttpFunc) (ctx: HttpContext) ->
        ctx.SetHttpHeader key value
        next ctx

中间件对HttpRequest和/或HttpResponse对象执行某些操作,然后调用next处理器以**继续**执行管道。

它也可以实现为EndpointHandler

let setHttpHeader key value : EndpointHandler =
    fun (ctx: HttpContext) ->
        ctx.SetHttpHeader key value
        Task.CompletedTask

如果这种处理程序在管道**中间**被使用,下一个处理程序将被调用,因为ctx.Response.HasStarted将返回false。如果它在管道**末尾**,则响应将开始,因为没有下一个处理器被调用。

提前返回

有时,EndpointHandlerEndpointMiddleware想要提前返回,而不是继续剩余的管道。

一个典型的例子是一个身份验证或授权处理程序,如果用户未通过身份验证,它不会继续剩余的管道。相反,它可能想要返回一个401 Unauthorized响应

let checkUserIsLoggedIn : EndpointMiddleware =
    fun (next: EndpointHandler) (ctx: HttpContext) ->
        if isNotNull ctx.User && ctx.User.Identity.IsAuthenticated then
            next ctx
        else
            setStatusCode 401 ctx
            Task.CompletedTask

else子句中,checkUserIsLoggedIn处理程序返回一个401 Unauthorized HTTP响应,并通过不调用next而是一个已经完成的任务来跳过剩余的EndpointHandler管道。

如果您将EndpointMiddleware定义为一个带有task {} CE的示例,则可以按照以下方式重写它

let checkUserIsLoggedIn : EndpointMiddleware =
    fun (next: EndpointHandler) (ctx: HttpContext) ->
        task {
            if isNotNull ctx.User && ctx.User.Identity.IsAuthenticated then
                return! next ctx
            else
                return ctx.SetStatusCode 401
        }

还可以使用EndpointHandler来实现这一点,但是必须显式启动响应

let checkUserIsLoggedIn : EndpointHandler =
    fun (ctx: HttpContext) ->
        if isNotNull ctx.User && ctx.User.Identity.IsAuthenticated then
            Task.CompletedTask
        else
            ctx.SetStatusCode 401
            text "Unauthorized" ctx // start response

基础

将 Oxpecker 插入 ASP.NET Core

安装Oxpecker NuGet包

PM> Install-Package Oxpecker

创建一个网络应用,并将其连接到ASP.NET Core中间件

open Oxpecker

// usually your application consists of several routes
let webApp = [
    route "/"       <| text "Hello world"
    route "/ping"   <| text "pong"
]
// sometimes it can only be a single route
let webApp1 = route "/" <| "Hello Oxpecker"
// or it can only be a single "MultiEndpoint" route
let webApp2 = GET [
    route "/" <| "Hello Oxpecker"
]

let configureApp (appBuilder: IApplicationBuilder) =
    appBuilder
        .UseRouting()
        .UseOxpecker(webApp) // Add Oxpecker to the ASP.NET Core pipeline, should go after UseRouting
        //.UseOxpecker(webApp1) will work
        //.UseOxpecker(webApp2) will also work
    |> ignore

let configureServices (services: IServiceCollection) =
    services
        .AddRouting()
        .AddOxpecker() // Register default Oxpecker dependencies
    |> ignore

[<EntryPoint>]
let main _ =
    let builder = WebApplication.CreateBuilder(args)
    configureServices builder.Services
    let app = builder.Build()
    configureApp app
    app.Run()
    0

依赖关系管理

ASP.NET Core 内置了依赖管理功能,与Oxpecker开箱即用

注册服务

与任何其他ASP.NET Core网络应用一样,注册服务的方式相同

let configureServices (services : IServiceCollection) =
    // Add default Oxpecker dependencies
    services.AddOxpecker() |> ignore

    // Add other dependencies
    // ...

检索服务

您可以通过Oxpecker的EndpointHandler函数中的内置服务定位器(RequestServices)来检索已注册的服务,该服务包含一个HttpContext对象

let someHandler : EndpointHandler =
    fun (ctx: HttpContext) ->
        let fooBar =
            ctx.RequestServices.GetService(typeof<IFooBar>)
            :?> IFooBar
        // Do something with `fooBar`...
        // Return a Task

Oxpecker还有一个名为GetService<'T>的附加HttpContext扩展方法,可以使代码更简洁

let someHandler : EndpointHandler =
    fun (ctx: HttpContext) ->
        let fooBar = ctx.GetService<IFooBar>()
        // Do something with `fooBar`...
        // Return a Task

还有一些扩展方法可以用于检索默认依赖项,例如IWebHostEnvironmentILogger对象,有关这些内容,请参阅本文件的相应部分。

功能化依赖注入

但是,如果您更愿意使用更功能化的依赖注入方法,则不应使用基于容器的策略,而应遵循Env策略。

该方法在文章https://medium.com/@lanayx/dependency-injection-in-f-the-missing-manual-d376e9cafd0f 中进行了描述,并且要了解其实践方式,您可以参考存储库中的CRUD示例

多个环境和配置

ASP.NET Core内建了对多个环境的处理配置管理的支持,它们都与Oxpecker配合工作。

此外,Oxpecker提供了一个名为GetHostingEnvironment()的扩展方法,可以用于轻松地从EndpointHandler函数中检索IWebHostEnvironment对象

let someHandler : EndpointHandler =
    fun (ctx: HttpContext) ->
        let env = ctx.GetHostingEnvironment()
        // Do something with `env`...
        // Return a Task

可以通过GetService<'T>扩展方法检索配置选项

let someHandler : EndpointHandler =
    fun (ctx: HttpContext) ->
        let settings = ctx.GetService<IOptions<MySettings>>()
        // Do something with `settings`...
        // Return a Task

如果您需要在配置服务时访问配置,可以按照如下方式进行访问

let configureServices (services: IServiceCollection) =
    let serviceProvider = services.BuildServiceProvider()
    let settings = serviceProvider.GetService<IConfiguration>()
    // Configure services using the `settings`...
    services.AddOxpecker() |> ignore

日志记录

ASP.NET Core内建了一个与Oxpecker开箱即用的日志记录API

在EndpointHandler函数中记录日志

您可以通过GetLogger<'T>GetLogger (categoryName : string)扩展方法检索一个ILogger对象(可以用于记录日志)。

let someHandler : EndpointHandler =
    fun (ctx: HttpContext) ->
        // Retrieve an ILogger through one of the extension methods
        let loggerA = ctx.GetLogger<ModuleName>()
        let loggerB = ctx.GetLogger("someHandler")

        // Log some data
        loggerA.LogCritical("Something critical")
        loggerB.LogInformation("Logging some random info")
        // etc.

        // Return a Task

错误处理

Oxpecker没有内建的错误处理或未找到处理机制,因为它可以很容易地使用以下函数实现,这些函数应该在Oxpecker中间件之前和之后注册

// error handling middleware
let errorHandler (ctx: HttpContext) (next: RequestDelegate) =
    task {
        try
            return! next.Invoke(ctx)
        with
        | :? ModelBindException
        | :? RouteParseException as ex ->
            let logger = ctx.GetLogger()
            logger.LogWarning(ex, "Unhandled 400 error")
            ctx.SetStatusCode StatusCodes.Status400BadRequest
            return! ctx.WriteHtmlView(errorView 400 (string ex))
        | ex ->
            let logger = ctx.GetLogger()
            logger.LogError(ex, "Unhandled 500 error")
            ctx.SetStatusCode StatusCodes.Status500InternalServerError
            return! ctx.WriteHtmlView(errorView 500 (string ex))
    } :> Task

// not found terminal middleware
let notFoundHandler (ctx: HttpContext) =
    let logger = ctx.GetLogger()
    logger.LogWarning("Unhandled 404 error")
    ctx.SetStatusCode 404
    ctx.WriteHtmlView(errorView 404 "Page not found!")

///...

let configureApp (appBuilder: IApplicationBuilder) =
    appBuilder
        .UseRouting()
        .Use(errorHandler) // Add error handling middleware BEFORE Oxpecker
        .UseOxpecker(endpoints)
        .Run(notFoundHandler) // Add not found middleware AFTER Oxpecker

Web 请求处理

Oxpecker附带了一组默认的HttpContext扩展方法和默认的EndpointHandler函数,可用于构建丰富的网络应用。

HTTP 头

在Oxpecker中处理HTTP头很直接。扩展方法TryGetHeaderValue (key: string)尝试检索给定HTTP头的值,然后返回Some string或者None

let someHandler : EndpointHandler =
    fun (ctx: HttpContext) ->
        let someValue =
            match ctx.TryGetHeaderValue "X-MyOwnHeader" with
            | None -> "default value"
            | Some headerValue -> headerValue

        // Do something with `someValue`...
        // Return a Task

可以通过扩展方法SetHttpHeader (key: string) (value: obj)设置响应中的HTTP头

let someHandler : EndpointHandler =
    fun (ctx: HttpContext) ->
        ctx.SetHttpHeader "X-CustomHeader" "some-value"
        // Do other stuff...
        // Return a Task

您还可以通过setHttpHeader HTTP处理程序设置HTTP头

let customHeader : EndpointHandler =
    setHttpHeader "X-CustomHeader" "Some value"

let webApp = [
    route "/foo" (customHeader >=> text "Foo")
]

请注意,这些是Oxpecker的附加功能,它们补充了ASP.NET Core框架中现有的HTTP响应头功能。ASP.NET Core通过ctx.Request.GetTypedHeaders()方法提供了更高级的HTTP响应头功能。

HTTP 动词

Oxpecker公开了一组功能,可以基于请求的HTTP动词过滤请求

  • GET
  • POST
  • PUT
  • PATCH
  • DELETE
  • HEAD
  • OPTIONS
  • TRACE
  • CONNECT

还有一个额外的GET_HEAD处理程序,可以同时过滤HTTP的GETHEAD请求。

根据HTTP动词过滤请求在实现根据动词(例如GETPOST)行为不同的路由时非常有用。

let submitFooHandler : EndpointHandler =
    // Do something

let submitBarHandler : EndpointHandler =
    // Do something

let webApp = [
    // Filters for GET requests
    GET [
        route "/foo" <| text "Foo"
        route "/bar" <| text "Bar"
    ]
    // Filters for POST requests
    POST [
        route "/foo" <| submitFooHandler
        route "/bar" <| submitBarHandler
    ]
]

如果您需要从EndpointHandler函数内部检查请求的HTTP动词,则可以使用默认的ASP.NET Core HttpMethods类。

open Microsoft.AspNetCore.Http

let someHandler : EndpointHandler =
    fun (ctx: HttpContext) ->
        if HttpMethods.IsPut ctx.Request.Method then
            // Do something
        else
            // Do something else
        // Return a Task

GET_HEAD是一个特殊函数,可以用来同时启用资源的GETHEAD请求。在启用缓存且客户端可能想要在发出GET请求之前发送HEAD请求以检查ETagLast-Modified HTTP响应头时,这非常有用。

您还可以使用applyHttpVerbsToEndpoints函数创建自定义HTTP动词组合。

let GET_HEAD_OPTIONS: Endpoint seq -> Endpoint =
    applyHttpVerbsToEndpoints(Verbs [ HttpVerb.GET; HttpVerb.HEAD; HttpVerb.OPTIONS ])

let webApp = [
    GET_HEAD_OPTIONS [
        route "/foo" <| text "Foo"
        route "/bar" <| text "Bar"
    ]
]

HTTP 状态码

设置响应的HTTP状态码可以通过SetStatusCode (httpStatusCode: int)扩展方法或使用setStatusCode (statusCode: int)函数完成。

let someHandler : EndpointHandler =
    fun (ctx: HttpContext) ->
        ctx.SetStatusCode 200
        // Return a Task

// or...

let someHandler : EndpointHandler =
    setStatusCode 200
    >=> text "Hello World"

路由

Oxpecker提供了一些路由功能来满足大多数使用场景。请注意,Oxpecker路由建立在ASP.NET Core端点路由之上,因此所有路由都不区分大小写。

route

可以通过route HTTP处理程序以最简单的方式执行路由。

let webApp = [
    route "/foo" <| text "Foo"
    route "/bar" <| text "Bar"
]
routef

如果路由包含用户定义的参数,则可以使用routef HTTP处理程序。

let fooHandler first last age : EndpointHandler =
    fun (ctx: HttpContext) ->
        (sprintf "First: %s, Last: %s, Age: %i" first last age
        |> text) ctx

let webApp = [
    routef "/foo/{%s}/{%s}/{%i}" fooHandler
    routef "/bar/{%O:guid}" (fun (guid: Guid) -> text (string guid))
]

routef HTTP处理程序接受两个参数 - 一个格式字符串和一个EndpointHandler函数。

格式字符串支持以下格式字符

格式字符 类型
%b bool
%c char
%s string
%i int
%d int64
%f float/double
%u uint64
%O Any object(带有约束

注意routef处理程序只能处理最多5个路由参数。不建议在路由中使用超过3个参数,但如果确实需要很多,可以使用带有利用.TryGetRouteValue扩展方法的EndpointHandler函数的route

route "/{a}/{b}/{c}/{d}/{e}/{f}" (fun ctx ->
    let a = ctx.TryGetRouteValue("a") |> Option.defaultValue ""
    let b = ctx.TryGetRouteValue("b") |> Option.defaultValue ""
    let c = ctx.TryGetRouteValue("c") |> Option.defaultValue ""
    let d = ctx.TryGetRouteValue("d") |> Option.defaultValue ""
    let e = ctx.TryGetRouteValue("e") |> Option.defaultValue ""
    let f = ctx.TryGetRouteValue("f") |> Option.defaultValue ""
    text (sprintf "%s %s %s %s %s %s", a b c d e f) ctx
)
subRoute

它允许您在不重复已预先过滤的路由部分的情况下对路由进行分类。

let webApp =
    subRoute "/api" [
        subRoute "/v1" [
            route "/foo" <| text "Foo 1"
            route "/bar" <| text "Bar 1"
        ]
        subRoute "/v2" [
            route "/foo" <| text "Foo 2"
            route "/bar" <| text "Bar 2"
        ]
    ]

在此示例中,检索“Bar 2”的最终URL为http[s]://your-domain.com/api/v2/bar

addMetadata

它允许您向路由添加元数据,这些元数据可以在管道的后续部分中使用。

let webApp =
    GET [
        route "/foo" (text "Foo") |> addMetadata "foo"
    ]
configureEndpoint

此函数允许您使用ASP.NET .With*扩展方法配置端点。

let webApp =
    GET [
        route "/foo" (text "Foo")
          |> configureEndpoint
             _.WithMetadata("foo")
              .WithDisplayName("Foo")
    ]

查询字符串

在Oxpecker中与查询字符串的工作方式类似于处理HTTP响应头。扩展方法TryGetQueryValue (key : string)尝试检索给定查询字符串参数的值,然后返回Some stringNone

let someHandler : EndpointHandler =
    fun (ctx: HttpContext) ->
        let someValue =
            match ctx.TryGetQueryValue "q" with
            | None   -> "default value"
            | Some q -> q

        // Do something with `someValue`...
        // Return a Task

您还可以通过ctx.Request.Query对象访问查询字符串,该对象返回一个IQueryCollection对象,允许您对其进行更多操作。

最后但同样重要的是,还有一个名为BindQuery<'T>HttpContext扩展方法,它允许您将整个查询字符串绑定到类型为'T的对象(请参阅绑定查询字符串)。

模型绑定

Oxpecker默认提供了一些HttpContext扩展方法和等价的EndpointHandler函数,这使得可以将HTTP请求的有效负载或查询字符串绑定到自定义对象。

绑定 JSON

可以使用BindJson[T]()扩展方法将JSON有效负载绑定到类型的对象。

[<CLIMutable>]
type Car = {
    Name   : string
    Make   : string
    Wheels : int
    Built  : DateTime
}

let submitCar : EndpointHandler =
    fun (ctx: HttpContext) ->
        task {
            // Binds a JSON payload to a Car object
            let! car = ctx.BindJson<Car>()

            // Sends the object back to the client
            return! ctx.Write <| TypedResults.Ok car
        }

let webApp = [
    GET [
        route "/"    <| text "index"
        route "ping" <| text "pong"
    ]
    POST [
        route "/car" submitCar
    ]
]

或者,您也可以使用bindJson http处理程序。

[<CLIMutable>]
type Car = {
    Name   : string
    Make   : string
    Wheels : int
    Built  : DateTime
}

let webApp = [
    GET [
        route "/"    <| text "index"
        route "ping" <| text "pong"
    ]
    POST [
        route "/car" (bindJson<Car> (fun car -> %TypedResults.Ok car))
    ]
]

这两种方式,无论是HttpContext扩展方法还是EndpointHandler函数,都会尝试创建类型的实例,不管提交的有效负载是否包含了对的完整表示。解析后的对象可能只包含部分数据(一些属性可能是null),在进一步处理之前可能需要额外的null检查。

请注意,为了让模型绑定工作,记录类型必须用[<CLIMutable>]属性装饰,以确保类型具有无参构造函数。

底层的JSON序列化器可以在应用程序启动时作为依赖项进行配置(参见JSON)。

绑定表单

扩展方法BindForm[T] (?cultureInfo : CultureInfo)可以将表单数据绑定到类型的对象。您还可以指定一个可选的CultureInfo对象,用于解析如DateTime对象或浮点数等特定于文化的数据。

[<CLIMutable>]
type Car = {
    Name   : string
    Make   : string
    Wheels : int
    Built  : DateTime
}

let submitCar : EndpointHandler =
    fun (ctx: HttpContext) ->
        task {
            // Binds a form payload to a Car object
            let! car = ctx.BindForm<Car>()

            // or with a CultureInfo:
            let british = CultureInfo.CreateSpecificCulture("en-GB")
            let! car2 = ctx.BindForm<Car>(british)

            // Sends the object back to the client
            return! ctx.Write <| Ok car
        }

let webApp = [
    GET [
        route "/"    <| text "index"
        route "ping" <| text "pong"
    ]
    POST [ route "/car" submitCar ]
]

或使用bindFormbindFormC(额外的文化参数)http处理程序。

[<CLIMutable>]
type Car = {
    Name   : string
    Make   : string
    Wheels : int
    Built  : DateTime
}

let british = CultureInfo.CreateSpecificCulture("en-GB")

let webApp = [
    GET [
        route "/"    <| text "index"
        route "ping" <| text "pong"
    ]
    POST [
        route "/car" (bindForm<Car> (fun model -> %Ok model))
        route "/britishCar" (bindFormC<Car> british (fun model -> %Ok model))
    ]
]

与前面的示例类似,记录类型必须用[<CLIMutable>]属性装饰,以便模型绑定工作。

绑定查询字符串

扩展方法BindQuery[T] (?cultureInfo: CultureInfo)可以将查询字符串参数绑定到类型的对象。可以指定一个可选的CultureInfo对象,以解析如DateTime对象和浮点数等特定于文化的数据。

[<CLIMutable>]
type Car = {
    Name   : string
    Make   : string
    Wheels : int
    Built  : DateTime
}

let submitCar : EndpointHandler =
    fun (ctx: HttpContext) ->
        // Binds the query string to a Car object
        let car = ctx.BindQuery<Car>()

        // or with a CultureInfo:
        let british = CultureInfo.CreateSpecificCulture("en-GB")
        let car2 = ctx.BindQuery<Car>(british)

        // Sends the object back to the client
        ctx.Write <| Ok car

let webApp = [
    GET [
        route "/"    <| text "index"
        route "ping" <| text "pong"
        route "/car" <| submitCar
    ]
]

或使用bindQuerybindQueryC(额外的文化参数)http处理程序。

[<CLIMutable>]
type Car = {
    Name   : string
    Make   : string
    Wheels : int
    Built  : DateTime
}

let british = CultureInfo.CreateSpecificCulture("en-GB")

let webApp = [
    GET [
        route "/"    <| text "index"
        route "ping" <| text "pong"
    ]
    POST [
        route "/car" (bindQuery<Car> (fun model -> %Ok model))
        route "/britishCar" (bindQueryC<Car> british (fun model -> %Ok model))
    ]
]

与前面的示例类似,记录类型必须用[<CLIMutable>]属性装饰,以便模型绑定工作。

文件上传

ASP.NET Core使得处理上传的文件变得非常简单。

可以使用HttpContext.Request.Form.Files集合处理一个或多个客户端发送的小文件。

let fileUploadHandler : EndpointHandler =
    fun (ctx: HttpContext) ->
        match ctx.Request.HasFormContentType with
        | false ->
            ctx.Write <| BadRequest()
        | true  ->
            ctx.Request.Form.Files
            |> Seq.fold (fun acc file -> $"{acc}\n{file.FileName}") ""
            |> ctx.WriteText

let webApp = [ route "/upload" fileUploadHandler ]

您也可以通过利用IFormFeatureReadFormAsync方法来读取上传的文件。

let fileUploadHandler : EndpointHandler =
    fun (ctx: HttpContext) ->
        task {
            let formFeature = ctx.Features.Get<IFormFeature>()
            let! form = formFeature.ReadFormAsync CancellationToken.None
            return!
                form.Files
                |> Seq.fold (fun acc file -> $"{acc}\n{file.FileName}") ""
                |> ctx.WriteText
        }

let webApp = [ route "/upload" fileUploadHandler ]

对于大文件上传,建议流式传输文件,以避免资源耗尽。

有关在ASP.NET Core中处理大文件上传的更多信息,请参阅StackOverflow上的大文件上传

WebSockets

Oxpecker不提供任何额外的包装,完全依赖于ASP.NET Core WebSocket支持

let configureApp (appBuilder: IApplicationBuilder) =
    appBuilder
        .UseRouting()
        .UseOxpecker(webApp) // Add Oxpecker
        .UseWebSockets() // Add WebSockets
    |> ignore

身份验证和授权

Oxpecker的安全模型与Minimal API安全模型相同,请确保您非常熟悉它。主要区别在于,在Oxpecker中,您可以在单个端点和一组端点上方便地调用configureEndpoint _.RequireAuthorization

let webApp = [
    // single endpoint
    route "/" (text "Hello World")
        |> configureEndpoint
            _.DisableAntiforgery()
             .RequireAuthorization()
    // endpoint group
    GET [
        route "/index" <| text "index"
        route "/ping"  <| text "pong"
    ] |> configureEndpoint _.RequireAuthorization(
            AuthorizeAttribute(AuthenticationSchemes = "MyScheme")
        )
]

条件请求

条件HTTP头(例如,If-MatchIf-Modified-Since等)是提高性能(Web缓存)、解决丢失更新问题或执行乐观并发控制时,客户端从Web服务器请求资源的一种常见模式。

Oxpecker提供validatePreconditions端点处理程序,可用于对给定HTTP请求的ETag和/或Last-Modified值进行预验证检查。

let someHandler (eTag         : string)
                (lastModified : DateTimeOffset)
                (content      : string) =
    let eTagHeader = Some (EntityTagHelper.createETag eTag)
    validatePreconditions eTagHeader (Some lastModified) >=> text content

validatePreconditions 中间件接受两个可选参数 - 一个 eTag 和一个 lastModified 日期时间值 - 这些参数将被用于验证一个条件 HTTP 请求。如果所有条件都满足,或者没有提交任何条件,则调用 Oxpecker 管道中的下一个 next http 处理器。否则,如果其中一个预条件失败或自上次检查以来资源未更改,则返回一个 412 预条件失败304 未修改 的响应。

ETag(实体标签) 值是由 web 服务器分配给 URL 中找到的资源特定版本的不可见标识符。 Last-Modified 值提供了一个时间戳,指示原始服务器认为选择的表示内容最后一次修改的日期和时间。

Oxpecker 的 validatePreconditions 端点中间件验证以下条件 HTTP 头部

  • If-Match
  • If-None-Match
  • If-Modified-Since
  • If-Unmodified-Since

If-Range HTTP 头部不会被作为 validatePreconditions http 处理器的一部分进行验证,因为它是一个特定于流的检查,由 Oxpecker 的 Streaming 功能处理。

或者 Oxpecker 提供了一个 HttpContext 扩展方法 ValidatePreconditions(eTag, lastModified),可以用来创建自定义的条件端点中间件。 ValidatePreconditions 方法接受相同的两个可选参数,并返回一个类型为 Precondition 的结果。

Precondition 联合类型包含以下情况

情况 描述和推荐操作
NoConditionsSpecified 没有发生验证,因为客户端没有发送任何条件 HTTP 头部。像往常一样处理 Web 请求。
ConditionFailed 至少有一个条件无法满足。建议向客户端返回 412 状态码(为此可以使用 HttpContext.PreconditionFailedResponse 方法)。
ResourceNotModified 自上次访问以来资源未更改。服务器可以跳过处理此请求并返回一个 304 状态码给客户端(为此可以使用 HttpContext.NotModifiedResponse 方法)。
AllConditionsMet 所有预条件都满足。服务器应继续正常处理请求。

validatePreconditions http 处理器和 ValidatePreconditions 扩展方法不仅验证所有条件 HTTP 头部,还根据 HTTP 规范设置所需的 ETag 和/或 Last-Modified HTTP 响应头。

这两个函数遵循最新的 HTTP 指南,并按照在 RFC 2616 中定义的正确优先级验证所有条件头。

示例:使用 HttpContext.ValidatePreconditions

// Pass an optional eTag and lastModified timestamp into the handler, because generating an eTag might require to load the entire resource into memory and therefore this is not something which should be done on every request.
let someHttpHandler eTag lastModified : EndpointHandler =
    fun (ctx: HttpContext) ->
        task {
            match ctx.ValidatePreconditions(eTag, lastModified) with
            | ConditionFailed     -> return ctx.PreconditionFailedResponse()
            | ResourceNotModified -> return ctx.NotModifiedResponse()
            | AllConditionsMet | NoConditionsSpecified ->
                // Continue as normal
                // Do stuff
        }

let webApp = [
    route "/"    <| text "Hello World"
    route "/foo" <| someHttpHandler None None
]

响应写入

在 Oxpecker 中向客户端发送响应可以通过一系列小的 HttpContext 扩展方法及其等效的 EndpointHandler 函数来完成。

写入字节数

WriteBytes (data: byte[]) 扩展方法和 bytes (data: byte[]) 端点处理器都将一个 byte 数组 写入 HTTP 请求的响应流。

let someHandler (data: byte[]) : EndpointHandler =
    fun (ctx: HttpContext) ->
        task {
            // Do stuff
            return! ctx.WriteBytes data
        }

// or...

let someHandler (data: byte[]) : EndpointHandler =
    // Do stuff
    bytes data

这两个函数还将设置 Content-Length HTTP 头为 byte 数组 的长度。

bytes http 处理器(及其 HttpContext 扩展方法等效)在您想为尚未由 Oxpecker 提供的特定媒体类型创建自己的响应写入函数时非常有用。

例如,Oxpecker 没有反序列化和将 YAML 响应写回客户端的功能。然而,您可以引用另一个第三方库,该库可以将对象序列化为 YAML 字符串,然后像这样创建自己的 yaml http 处理器

let yaml (x: obj) : EndpointHandler =
    setHttpHeader "Content-Type" "text/yaml"
    >=> bytes (x |> YamlSerializer.toYaml |> Encoding.UTF8.GetBytes)

写入文本

WriteText (str : string) 扩展方法和 text (str: string) 端点处理程序会将字符串以 UTF8 格式写入响应中,并在响应中设置 Content-Type HTTP 标头为 text/plain

let someHandler (str: string) : EndpointHandler =
    fun (ctx: HttpContext) ->
        task {
            // Do stuff
            return! ctx.WriteText str
        }

// or...

let someHandler (str: string) : EndpointHandler =
    // Do stuff
    text str
写入 JSON

WriteJson<'T> (dataObj : 'T) 扩展方法和 json<'T> (dataObj: 'T) 端点处理程序都会将对象序列化为 JSON 字符串,并将输出写入 HTTP 请求的响应流中。它们还将响应中的 Content-Length HTTP 标头和 Content-Type 标头设置为 application/json

let someHandler (animal: Animal) : EndpointHandler =
    fun (ctx: HttpContext) ->
        task {
            // Do stuff
            return! ctx.WriteJson animal
        }

// or...

let someHandler (animal: Animal) : EndpointHandler =
    // Do stuff
    json animal

WriteJsonChunked<'T> (dataObj: 'T) 扩展方法和 jsonChunked (dataObj: 'T) 端点处理程序将直接写入 HTTP 请求的响应流,而无需额外缓存到字节数组中。它们不会设置 Content-Length 标头,而是设置 Transfer-Encoding: chunked 标头和 Content-Type: application/json

let someHandler (person: Person) : EndpointHandler =
    fun (ctx: HttpContext) ->
        task {
            // Do stuff
            return! ctx.WriteJsonChunked person
        }

// or...

let someHandler (person: Person) : EndpointHandler =
    // Do stuff
    jsonChunked person

底层的 JSON 序列化器在应用程序启动时配置为依赖项,默认为 System.Text.Json(当您写入 services.AddOxpecker() 时)。您可以实现 Serializers.IJsonSerializer 接口来插入自定义 JSON 序列化器。

let configureServices (services : IServiceCollection) =
    // First register all default Oxpecker dependencies
    services.AddOxpecker() |> ignore
    // Now register custom serializer
    services.AddSingleton<Serializers.IJsonSerializer>(CustomSerializer()) |> ignore
    // or use default STJ serializer, but with different options
    services.AddSingleton<Serializers.IJsonSerializer>(
            SystemTextJson.Serializer(specificOptions)) |> ignore
写入 IResult

如果您喜欢 ASP.NET Core IResult 提供的特性,您可能会很高兴地知道,Oxpecker 也支持它。您可以使用 Microsoft.AspNetCore.Http.TypedResults 简化带有状态码的响应返回。

open Oxpecker
open type Microsoft.AspNetCore.Http.TypedResults

let johnDoe = {|
    FirstName = "John"
    LastName  = "Doe"
|}

let app = [
    route "/"     <| text "Hello World"
    route "/john" <| %Ok johnDoe // returns 200 OK with JSON body
    route "/bad"  <| %BadRequest() // returns 400 BadRequest with empty body
]

使用 % 运算符可以将 IResult 转换为 EndpointHandler。您还可以使用 .Write 扩展方法在 EndpointHandler 中执行转换。

let myHandler : EndpointHandler =
    fun (ctx: HttpContext) ->
        ctx.Write <| TypedResults.Ok johnDoe
写入 HTML 字符串

WriteHtmlString (html: string) 扩展方法和 htmlString (html: string) 端点处理程序等同于写入文本,但它们将 Content-Type 标头设置为 text/html

let someHandler (dataObj: obj) : EndpointHandler =
    fun (ctx: HttpContext) ->
        task {
            // Do stuff
            return! ctx.WriteHtmlString "<html><head></head><body>Hello World</body></html>"
        }

// or...

let someHandler (dataObj: obj) : EndpointHandler =
    // Do stuff
    htmlString "<html><head></head><body>Hello World</body></html>"
写入 HTML 视图

Oxpecker 为功能开发者提供自己的非常强大的视图引擎(请参阅Oxpecker 视图引擎)。WriteHtmlView (htmlView : HtmlElement) 扩展方法和 htmlView (htmlView : HtmlElement) HTTP 处理程序都将给定的 HTML 视图编译成有效的 HTML 代码,并将其写入 HTTP 请求的响应流中。此外,它们还将 Content-Length HTTP 标头设置为正确值,将 Content-Type 标头设置为 text/html

let indexView =
    html() {
        head() {
            title() { "Oxpecker" }
        }
        body() {
            h1(id="Header") { "Oxpecker" }
            p() { "Hello World." }
        }
    }

let someHandler : EndpointHandler =
    fun (ctx: HttpContext) ->
        task {
            // Do stuff
            return! ctx.WriteHtmlView indexView
        }

// or...

let someHandler : EndpointHandler =
    // Do stuff
    htmlView indexView

您还可以使用 htmlChunkedhtmlViewChunked HTTP 处理程序以及相应的 WriteHtmlChunkedWriteHtmlViewChunked 扩展方法来进行 HTML 流。

let indexView =
    html() {
        head() {
            title() { "Oxpecker" }
        }
        body() {
            h1(id="Header") { "Oxpecker" }
            p() { "Hello World." }
        }
    }

let someHandler : EndpointHandler =
    fun (ctx: HttpContext) ->
        task {
            // Do stuff
            return! ctx.WriteHtmlViewChunked indexView
        }

// or...

let someHandler : EndpointHandler =
    // Do stuff
    htmlViewChunked indexView

警告:虽然运行时速度快,但使用许多长的 CE 表达式可能会减慢您的项目编译和 IDE 体验(请参阅问题),因此您可能决定使用不同的视图引擎。有多种视图引擎可供选择:Giraffe.ViewEngine、Feliz.ViewEngine、Falco.Markup 或您可以自己编写!要插入外部视图引擎,您可以编写一个简单的扩展。

[<Extension>]
static member WriteMyHtmlView(ctx: HttpContext, htmlView: MyHtmlElement) =
    let bytes = htmlView |> convertToBytes
    ctx.Response.ContentType <- "text/html; charset=utf-8"
    ctx.WriteBytes bytes

// ...

let myHtmlView (htmlView: MyHtmlElement) : EndpointHandler =
    fun (ctx: HttpContext) -> ctx.WriteMyHtmlView htmlView

流式传输

有时必须将大文件或数据块发送到客户端,为了避免将整个数据加载到内存中,Oxpecker 网络应用程序可以使用流以更高效的方式发送响应。

可以用于将类型为 Stream 的对象以流的形式流式传输到客户端的 WriteStream 扩展方法和 streamData 端点处理程序。

这两个函数接受以下参数

  • enableRangeProcessing:如果为 true,则客户端可以请求流式传输数据的一个子范围(当客户端想要在暂停下载后继续流式传输、互联网连接丢失等情况时很有用)。
  • stream:要返回给客户端的流对象。
  • eTag:用于条件请求的实体标头标签(请参阅条件请求)。
  • lastModified:用于条件请求的最后修改时间戳(参见 条件请求)。

如果设置了eTaglastModified时间戳,那么在响应过程中这两个函数也会设置ETag和/或Last-Modified HTTP头。

let someStream : Stream = ...

let someHandler : EndpointHandler =
    fun (ctx: HttpContext) ->
        task {
            // Do stuff
            return! ctx.WriteStream(
                true, // enableRangeProcessing
                someStream,
                None, // eTag
                None) // lastModified
        }

// or...

let someHandler : EndpointHandler =
    // Do stuff
    streamData
        true // enableRangeProcessing
        someStream
        None // eTag
        None // lastModified

在大多数情况下,Web应用程序将希望直接从本地文件系统中流式传输文件。在这种情况下,你可以使用WriteFileStream扩展方法或streamFile HTTP处理程序,它们与WriteStreamstreamData相同,只不过它们接受一个相对路径或绝对路径的filePath而不是Stream对象。

let someHandler : EndpointHandler =
    fun (ctx: HttpContext) ->
        task {
            // Do stuff
            return! ctx.WriteFileStream(
                true, // enableRangeProcessing
                "large-file.zip",
                None, // eTag
                None) // lastModified
        }

// or...

let someHandler : EndpointHandler =
    // Do stuff
    streamFile
        true // enableRangeProcessing
        "large-file.zip"
        None // eTag
        None // lastModified

Oxpecker中的所有流式传输函数也将验证条件HTTP头,包括如果设置了enableRangeProcessingtrueIf-Range HTTP头。

重定向

可以用于在处理传入的Web请求时将客户端重定向到不同位置的redirectTo (location: string) (permanent: bool)端点处理程序。

let webApp = [
    route "/new" <| text "Hello World"
    route "/old" <| redirectTo "https://myserver.com/new" true
]

请注意,如果将permanent标志设置为true,则Oxpecker Web应用程序将向浏览器发送一个301 HTTP状态码,告诉它们重定向是永久的。这通常会导致浏览器缓存信息并且不再第二次点击已弃用的URL。如果不希望这样,请将permanent设置为false302 HTTP状态码),以确保浏览器在重定向到(临时的)新URL之前继续点击旧URL。

响应缓存

ASP.NET Core附带标准响应缓存中间件,与Oxpecker无缝协作。

如果您尚未使用两个ASP.NET Core元包之一(Microsoft.AspNetCore.AppMicrosoft.AspNetCore.All),则必须添加对Microsoft.AspNetCore.ResponseCaching NuGet包的额外引用。

添加NuGet包后,您需要在注册Oxpecker之前将其内的响应缓存中间件添加到应用程序的启动代码中。

let configureServices (services : IServiceCollection) =
    services
        .AddResponseCaching() // <-- Here the order doesn't matter
        .AddOxpecker()         // This is just registering dependencies
    |> ignore

let configureApp (app : IApplicationBuilder) =
    app
       .UseStaticFiles()     // Optional if you use static files
       .UseAuthentication()  // Optional if you use authentication
       .UseResponseCaching() // <-- Before UseOxpecker webApp
       .UseOxpecker webApp

在设置ASP.NET Core响应缓存中间件后,您可以使用Oxpecker的响应缓存HTTP处理程序将响应缓存添加到您的路由中。

// A test handler which generates a new GUID on every request
let generateGuidHandler : EndpointHandler =
    fun ctx -> ctx.WriteText(Guid.NewGuid().ToString())

let cacheHeader = Some <| CacheControlHeaderValue(MaxAge = TimeSpan.FromSeconds(30), Public = true)

let webApp = [
    route "/route1" (responseCaching cacheHeader None None >=> generateGuidHandler)
    route "/route2" (noResponseCaching >=> generateGuidHandler)
]

/route1的请求可以缓存长达30秒,而对/route2的请求将完全禁用响应缓存。

注意:如果您使用Postman测试上述代码,请确保您已在Postman中禁用无缓存功能,以便测试正确的缓存行为。

Oxpecker总共提供2个端点处理程序,可用于配置端点的响应缓存。

在上面的示例中,我们使用了noResponseCaching端点处理程序来完全禁用在客户端和任何代理服务器上的响应缓存。该noResponseCaching端点处理程序将在响应中发送以下HTTP头:

Cache-Control: no-store, no-cache
Pragma: no-cache
Expires: -1

responseCaching端点处理程序将在客户端和/或在代理服务器上启用响应缓存。该CacheControlHeaderValue对象将控制Cache-Control指令。

Public = true表示不仅允许客户端缓存给定缓存持续期的响应,还允许任何中介代理服务器以及ASP.NET Core中间件。这对于不包含任何特定用户数据、身份验证数据或任何cookies的HTTP GET/HEAD端点及其响应数据不频繁更改的情况很有用。

Public = false表示只有最终客户端可以存储给定缓存持续期的响应。代理服务器和ASP.NET Core响应缓存中间件不得缓存响应。

responseCaching 端点处理器有两个额外的参数:varyvaryByQueryKeys

变体

vary 参数指定哪些 HTTP 请求头必须受到尊重以更改缓存的响应。例如,如果端点根据客户端的 Accept 标头(内容协商)返回不同的响应(Content-Type),则在从缓存返回响应时必须考虑 Accept 标头。如果 web 服务器启用了响应压缩,则也适用同样的情况。如果响应根据客户端接受的压缩算法而变化,则缓存在提供来自缓存的响应时也必须尊重客户端的 Accept-Encoding HTTP 标头。

let cacheHeader = Some <| CacheControlHeaderValue(MaxAge = TimeSpan.FromSeconds(30), Public = true)

// Cache for 30 seconds without any vary headers
publicResponseCaching cacheHeader None None

// Cache for 30 seconds with Accept and Accept-Encoding as vary headers
publicResponseCaching cacheHeader (Some "Accept, Accept-Encoding") None
varyByQueryKeys

ASP.NET Core 响应缓存中间件提供了一项不是响应 HTTP 头的一部分的额外功能。默认情况下,如果路由可缓存,则中间件将尝试返回缓存的响应,即使查询参数不同也是如此。

例如,如果对 /foo/bar 的请求已经被缓存,那么如果请求 /foo/bar?query1=a/foo/bar?query1=a&query2=b,也会返回缓存的版本。

有时这可能不是所希望的,而 VaryByQueryKeys 功能允许中间件根据请求的查询键改变其缓存的响应。

通用的 responseCaching 端点处理器是最基本的响应缓存处理器,可用于配置自定义响应缓存处理器以及使用 VaryByQueryKeys 功能。

responseCaching
    (Some (CacheControlHeaderValue(MaxAge = TimeSpan.FromSeconds(30)))
    (Some "Accept, Accept-Encoding")
    (Some [| "query1"; "query2" |])

第一个参数是类型 CacheControlHeaderValue

第二个参数是一个 string option,它定义了 vary 参数。

第三个也是最后一个参数是一个 string[] option,它定义了一个可选的查询参数值列表,必须使用它来让 ASP.NET Core 响应缓存中间件通过这些参数值更改缓存的响应。请注意,此功能仅适用于 ASP.NET Core 响应缓存中间件,任何中间代理服务器都不会尊重它。

响应压缩

ASP.NET Core 拥有它自己的 响应压缩中间件,它可以直接与 Oxpecker 一起使用。无需附加的功能或 http 处理器,就可以使其与 Oxpecker web 应用程序一起使用。

测试

对 Oxpecker 应用程序的集成测试遵循 ASP.NET Core 测试 的概念。您可以在本存储库本身中查看测试示例:Oxpecker.Tests

产品 兼容和更多的计算目标框架版本。
.NET net8.0 兼容。 net8.0-android 已计算。 net8.0-browser 已计算。 net8.0-ios 已计算。 net8.0-maccatalyst 已计算。 net8.0-macos 已计算。 net8.0-tvos 已计算。 net8.0-windows 已计算。
兼容的目标框架
包含的目标框架(在包内)
更多关于目标框架.NET Standard的信息。

NuGet 包 (1)

显示依赖 Oxpecker 的前 1 个 NuGet 包

下载
Oxpecker.OpenApi

Oxpecker 的 OpenApi 支持

GitHub 仓库

此包没有被任何流行的 GitHub 仓库使用。

版本 下载 最后更新
0.13.1 78 8/13/2024
0.13.0 240 7/17/2024
0.12.0 91 7/16/2024
0.11.1 148 7/8/2024
0.11.0 87 7/5/2024
0.10.1 677 5/8/2024
0.10.0 223 4/29/2024
0.9.3 169 4/10/2024
0.9.2 125 4/5/2024
0.9.1 98 4/4/2024
0.9.0 118 3/23/2024
0.8.1 140 2/29/2024
0.7.1 199 2/12/2024
0.7.0 112 2/7/2024
0.6.1 84 2/5/2024
0.6.0 86 2/3/2024
0.5.1 93 1/26/2024
0.5.0 91 1/23/2024
0.4.0 98 1/22/2024
0.3.0 87 1/19/2024
0.2.0 92 1/15/2024
0.1.0 108 1/12/2024

更新 Oxpecker.ViewEngine 依赖