|
测试 Giraffe 应用程序遵循 ASP.NET Core 测试的概念。
必要的导入:
open Microsoft.AspNetCore.Builder
open Microsoft.AspNetCore.TestHost
open Microsoft.AspNetCore.Hosting
open System.Net.Http搭建测试主机:
let getTestHost() =
WebHostBuilder()
.UseTestServer()
.Configure(Action<IApplicationBuilder> [YourApp].configureApp)
.ConfigureServices([YourApp].configureServices)
.ConfigureLogging([YourApp].configureLogging)
.UseUrls([YourUrl])创建一个辅助函数来发出测试请求:
let testRequest (request : HttpRequestMessage) =
let resp = task {
use server = new TestServer(getTestHost())
use client = server.CreateClient()
let! response = request |> client.SendAsync
return response
}
resp.Result示例(使用 Xunit):
// Import needed for the code below:
open System.Net
[<Fact>]
let ``Hello world endpoint says hello`` () =
let response = testRequest (new HttpRequestMessage(HttpMethod.Get, &#34;/hello-world&#34;))
let content = response.Content.ReadAsStringAsync().Result
Assert.Equal(response.StatusCode, HttpStatusCode.OK)
Assert.Equal(content, &#34;hello&#34;)
[<Fact>]
let ``Example HTTP Post`` () =
let request = new HttpRequestMessage(HttpMethod.Post, &#34;/hello-world&#34;)
request.Content <- &#34;{\&#34;JsonField\&#34;:\&#34;JsonValue\&#34;}&#34;
let response = testRequest request
Assert.Equal(response.StatusCode, HttpStatusCode.OK)
// Check the json content除了默认的 HTTP 相关函数(例如 HttpContext 扩展方法和 HttpHandler 函数)之外,Giraffe 还提供了一些 Giraffe Web 应用程序通常需要的其他辅助函数。
短 GUID 和短 ID
ShortGuid 和 ShortId 模块提供辅助函数来处理 Giraffe 中的短 GUID 和短 ID。
短向导
ShortGuid.fromGuid 函数会将 System.Guid 转换为 URL 友好的 22 个字符长字符串值。
ShortGuid.toGuid 函数会将 22 个字符的短 GUID 字符串转换为有效的 System.Guid 对象。 在将字符串查询参数转换为有效的 Guid 参数时,此函数很有用:
let someHttpHandler : HttpHandler =
fun (next : HttpFunc) (ctx : HttpContext) ->
let guid =
match ctx.TryGetQueryStringValue &#34;id&#34; with
| None -> Guid.Empty
| Some shortGuid -> ShortGuid.toGuid shortGuid
// Do something with `guid`...
// Return a Task<HttpContext option>ShortId
ShortId.fromUInt64 函数会将 uint64 转换为 URL 友好的 11 字符长字符串值。
ShortId.toUInt64 函数会将 11 个字符的短 ID 字符串转换为 uint64 值。 在将字符串查询参数转换为有效的 uint64 参数时,此函数很有用:
let someHttpHandler : HttpHandler =
fun (next : HttpFunc) (ctx : HttpContext) ->
let id =
match ctx.TryGetQueryStringValue &#34;id&#34; with
| None -> 0UL
| Some shortId -> ShortId.toUInt64 shortId
// Do something with `id`...
// Return a Task<HttpContext option>短 GUID 和短 ID 也可以从路由参数中自动解析。
常用辅助函数
其他有用的 HttpContext 扩展方法
HttpContext 类型的 GetRequestUrl 扩展方法可用于检索 HTTP 请求的整个 URL 作为字符串值:
let someHandler : HttpHandler =
fun (next : HttpFunc) (ctx : HttpContext) ->
let requestUrl = ctx.GetRequestUrl()
text (sprintf &#34;The request URL is: %s&#34; requestUrl) next ctx日期时间扩展方法
Giraffe 自动向 DateTime 和 DateTimeOffset 对象添加两个扩展方法。 ToIsoString() 将给定的时间戳格式化为 RFC3339 格式的字符串,ToHtmlString() 将给定的时间戳格式化为 RFC822 格式的字符串:
let now = DateTimeOffset.UtcNow
let isoFormattedTimestamp = now.ToIsoString()
let now = DateTimeOffset.UtcNow
let htmlFormattedTimestamp = now.ToHtmlString()isNotNull
F# 语言提供了一个 isNull 函数,用于在与其他 .NET 语言互操作时检查空值。 不幸的是,默认情况下没有 isNotNull 函数。 Giraffe 通过提供额外的 isNotNull 函数弥补了这一差距:
if isNotNull someObj then
// ... do stuff here
else
// ... do other stuff herestrOption
在与其他 .NET 语言互操作时,F# 应用程序通常必须检查字符串值是否为 null。 在 F# 中用 null 表示可选缺失值是不自然的,因此 Giraffe 提供了 strOption 函数,该函数可以将字符串转换为 Option<string> 值以获得更自然的 F# 体验。 如果字符串为空,则 strOptoin 函数将返回 None,否则返回 Some string:
let someDateTime =
match strOption someString with
| Some str -> DateTimeOffset.Parse str
| None -> DateTimeOffset.UtcNowreadFileAsStringAsync
从本地文件系统读取文件通常是 Web 应用程序中的常见用例。 readFileAsStringAsync 函数将从本地文件系统异步读取给定文件路径的全部内容:
let someFunction =
task {
let! content = readFileAsStringAsync &#34;myfile.txt&#34;
// ... do stuff
}计算表达式
Giraffe 提供了两个额外的计算表达式,可以与 Option<&#39;T> 和 Result<&#39;T, &#39;TError> 对象一起使用。
opt {} 计算表达式可用于绑定选项,res {} 计算表达式可用于绑定结果对象:
open Giraffe.ComputationExpressions
let someHttpHandler : HttpHandler =
fun (next : HttpFunc) (ctx : HttpContext) ->
let result =
res {
let! header1 = ctx.GetRequestHeader &#34;X-Header-1&#34;
let! header2 = ctx.GetRequestHeader &#34;X-Header-2&#34;
let! header3 = ctx.GetRequestHeader &#34;X-Header-3&#34;
return (header1, header2, header3)
}
match result with
| Ok (h1, h2, h3) ->
sprintf &#34;%s, %s, %s&#34; h1 h2 h3
|> ctx.WriteTextAsync
| Error msg -> RequestErrors.BAD_REQUEST msg next ctx附加的功能
通过额外的 NuGet 包,Giraffe Web 应用程序可以使用更多功能:
端点路由
从 Giraffe 5.x 开始,我们引入了一个名为 Giraffe.EndpointRouting 的新模块。 端点路由模块实现了 Giraffe 默认路由功能的替代路由器,它与 ASP.NET Core 的端点路由 API 集成。
考虑到 ASP.NET Core 的端点路由的工作方式,与 Giraffe 的默认路由器相比,该模块有几个好处(不幸的是也有一些小缺点)。 Giraffe.EndpointRouting 的主要好处是它可以很好地与 ASP.NET Core 的其余部分集成,并且可以从 Endpoint Routing 使之成为可能的一切中受益。 这也意味着对 ASP.NET Core 路由器所做的任何性能改进都将直接转化为 Giraffe。 缺点是一些现有的路由功能无法移植到 Giraffe.EndpointRouting 并且默认情况下路由不区分大小写。 虽然这可能是某些应用程序的问题,但总体而言,限制很小,从长远来看,好处应该大大超过缺点。 终结点路由绝对是 ASP.NET Core 中路由的新首选选项,毫无疑问,多年来 ASP.NET 团队将进行大量投资和改进。
最后,可以让 Giraffe.EndpointRouting 模块和 Giraffe 的默认路由器并排工作,在可能的情况下从端点路由中受益,并在其他地方保留默认路由器。
端点路由基础
为了使用 Giraffe 的端点路由功能,必须首先打开所需的模块:
open Giraffe.EndpointRoutingGiraffe 的 HTTP 处理程序保持不变,无论它们是从典型的 Giraffe 路由器还是 Giraffe.EndpointRouting 模块使用的。 这使得移植到 Giraffe.EndpointRouting 模块非常容易:
let handler1 : HttpHandler =
fun (_ : HttpFunc) (ctx : HttpContext) ->
ctx.WriteTextAsync &#34;Hello World&#34;与 Giraffe 的默认路由器(实际上只是一个大的 HttpHandler 函数,通常在 choose 函数的帮助下实现)不同,端点路由器需要一个简单的端点函数列表:
let endpoints =
[
GET [
route &#34;/&#34; (text &#34;Hello World&#34;)
routef &#34;/%s/%i&#34; handler2
routef &#34;/%s/%s/%s/%i&#34; handler3
]
subRoute &#34;/sub&#34; [
// Not specifying a http verb means it will listen to all verbs
route &#34;/test&#34; handler1
]
]然后 Endpoint 列表必须使用 ASP.NET Core 的 EndpointMiddleware 进行初始化,而不是传递到 GiraffeMiddleware 中:
let configureApp (appBuilder : IApplicationBuilder) =
appBuilder
.UseRouting()
.UseEndpoints(fun e -> e.MapGiraffeEndpoints(endpoints))
|> ignore主要区别是:
除了 HttpHandler 函数之外,还有一个名为 Endpoint 的新类型
路由器是端点功能的平面列表
GET、POST、路由等函数将常规的 HttpHandler 映射到 Endpoint 函数(当 Giraffe.EndpointRouting 模块已打开时)
最终端点列表必须传递到 ASP.NET Core 的 EndpointMiddleware 而不是使用 GiraffeMiddleware
MapGiraffeEndpoints 扩展方法将这些函数转换为 EndpointMiddleware 所依赖的最终 RequestDelegate 函数,因此 Giraffe.EndpointRouting 模块不会为 ASP.NET Core 的端点路由解析添加任何额外的开销或运行时成本。
端点路由功能
以下路由功能可作为 Giraffe.EndpointRouting 模块的一部分使用:
GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS, TRACE, CONNECT
route
routef
subRoute
route、routef 和 subRoute 处理程序都是不区分大小写的。 Giraffe.EndpointRouting 模块不支持其他处理程序,例如 routex、subRoutef 或 choose。
选择处理程序被组成端点列表所取代。
其他路由处理程序无法像这样移植,但 ASP.NET Core 端点路由 API 允许通过公开有用的辅助函数来更好地控制和更好地了解端点。
使用 GetRouteData 扩展方法,可以从处理程序中访问路由值和数据令牌:
open Microsoft.AspNetCore.Routing
let myHandler (foo : int, bar : string) : HttpHandler =
fun (next : HttpFunc) (ctx : HttpContext) ->
let routeData = ctx.GetRouteData()
routeData.Values // Values produced on the current path
routeData.DataTokens // Tokens produced on the current path
sprintf &#34;Yada Yada %i %s&#34; foo bar
|> ctx.WriteTextAsyncGetEndpoint 扩展方法返回当前执行路径的端点,可用于进一步探索附加到该端点的元数据和其他数据:
let myHandler (foo : int, bar : string) : HttpHandler =
fun (next : HttpFunc) (ctx : HttpContext) ->
let endpoint = ctx.GetEndpoint()有关 ASP.NET Core 端点路由的更多信息,请参阅官方文档。
令牌路由器
Giraffe.TokenRouter NuGet 包公开了一个基于基数树顶部的替代路由 HttpHandler。 多个路由处理程序(例如:routef 和 subRoute)已被覆盖,路径匹配和值解析比使用基本的选择功能快得多。
此实现假设额外的内存和编译时间不是问题。 如果需要解析和路径匹配的速度和性能,那么 Giraffe.TokenRouter 可能更合适。
请查看官方 Giraffe TokenRouter GitHub 存储库以获取更多信息。
Razor
Giraffe.Razor NuGet 包为 Giraffe Web 应用程序添加了功能齐全的 Razor 支持。
有关详细信息,请访问官方 Giraffe Razor GitHub 存储库。
DotLiquid
Giraffe.DotLiquid NuGet 包将 DotLiquid 支持添加到 Giraffe Web 应用程序。
有关详细信息,请访问官方 Giraffe DotLiquid GitHub 存储库。
特别提及
Saturn
Saturn 是一个建立在 Giraffe 之上的自以为是的 Web 开发框架,它为 F# 实现了服务器端的功能性 MVC 模式。
Saturn 不是 Giraffe 的直接组成部分,而是在 Giraffe 之上构建了受 Phoenix 启发的 MVC 模式。 它由 Ionide 项目的作者开发和维护。 |
|