You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

VB.NET 4.7.1 Web Forms集成第三方API及UI片段最佳实践问询

VB.NET 4.7.1 Web Forms 集成第三方API(Keycloak认证)最佳实践

核心原则

绝对不要将Keycloak访问令牌暴露到前端,所有令牌获取、API调用逻辑必须放在后端执行,前端仅通过后端代理获取最终需要的UI数据或片段。


1. 后端HttpClient单例配置

Web Forms环境下,HttpClient不能每次请求都实例化(会耗尽连接池),推荐用单例模式封装:

Imports System.Net.Http
Imports System.Net.Http.Headers

Public NotInheritable Class HttpClientSingleton
    Private Shared ReadOnly _instance As HttpClient

    Shared Sub New()
        _instance = New HttpClient()
        ' 设置基础请求头
        _instance.DefaultRequestHeaders.Accept.Add(
            New MediaTypeWithQualityHeaderValue("application/json"))
    End Sub

    Public Shared ReadOnly Property Instance() As HttpClient
        Get
            Return _instance
        End Get
    End Property
End Class

2. Keycloak令牌获取与后端缓存

在后端实现令牌获取逻辑,并用Cache缓存令牌(提前30秒过期,避免边界失效问题):

Imports System.Web.Caching
Imports System.Net.Http
Imports System.Threading.Tasks

Public Class KeycloakTokenService
    Private Const TokenCacheKey As String = "Keycloak_Access_Token"
    Private ReadOnly _tokenEndpoint As String = "https://your-keycloak-domain/auth/realms/your-realm/protocol/openid-connect/token"
    Private ReadOnly _clientId As String = "your-client-id"
    Private ReadOnly _clientSecret As String = "your-client-secret"

    Public Async Function GetValidAccessTokenAsync() As Task(Of String)
        ' 优先从缓存取有效令牌
        Dim cachedToken As String = TryCast(HttpContext.Current.Cache(TokenCacheKey), String)
        If Not String.IsNullOrEmpty(cachedToken) Then
            Return cachedToken
        End If

        ' 请求新令牌
        Dim formData As New Dictionary(Of String, String) From {
            {"grant_type", "client_credentials"},
            {"client_id", _clientId},
            {"client_secret", _clientSecret}
        }

        Using content = New FormUrlEncodedContent(formData)
            Dim response = Await HttpClientSingleton.Instance.PostAsync(_tokenEndpoint, content)
            response.EnsureSuccessStatusCode()

            Dim tokenResponse = Await response.Content.ReadAsAsync(Of Dictionary(Of String, Object))()
            Dim accessToken = tokenResponse("access_token").ToString()
            Dim expiresIn = Convert.ToInt32(tokenResponse("expires_in"))

            ' 缓存令牌
            HttpContext.Current.Cache.Insert(
                TokenCacheKey,
                accessToken,
                Nothing,
                DateTime.Now.AddSeconds(expiresIn - 30),
                Cache.NoSlidingExpiration)

            Return accessToken
        End Using
    End Function
End Class

3. 封装第三方API调用服务

把GET/POST请求封装成后端方法,自动携带令牌:

Imports System.Net.Http
Imports System.Threading.Tasks

Public Class ThirdPartyApiService
    Private ReadOnly _tokenService As New KeycloakTokenService()
    Private ReadOnly _apiBaseUrl As String = "https://third-party-api-domain/api/v1/"

    ' GET请求:获取UI片段所需数据
    Public Async Function FetchDataForUIFragmentAsync() As Task(Of String)
        Dim accessToken = Await _tokenService.GetValidAccessTokenAsync()
        Dim requestUrl = $"{_apiBaseUrl}/data/fragment"

        HttpClientSingleton.Instance.DefaultRequestHeaders.Authorization = 
            New AuthenticationHeaderValue("Bearer", accessToken)

        Dim response = Await HttpClientSingleton.Instance.GetAsync(requestUrl)
        response.EnsureSuccessStatusCode()

        Return Await response.Content.ReadAsStringAsync()
    End Function

    ' POST请求:提交数据并获取结果
    Public Async Function PostDataForUIFragmentAsync(payload As String) As Task(Of String)
        Dim accessToken = Await _tokenService.GetValidAccessTokenAsync()
        Dim requestUrl = $"{_apiBaseUrl}/action/submit"

        HttpClientSingleton.Instance.DefaultRequestHeaders.Authorization = 
            New AuthenticationHeaderValue("Bearer", accessToken)

        Using content = New StringContent(payload, Encoding.UTF8, "application/json")
            Dim response = Await HttpClientSingleton.Instance.PostAsync(requestUrl, content)
            response.EnsureSuccessStatusCode()

            Return Await response.Content.ReadAsStringAsync()
        End Using
    End Function
End Class

4. 前端通过后端代理获取数据

创建ASHX处理程序作为中间层,前端AJAX调用该端点获取数据:

ASHX处理程序示例(ApiProxy.ashx)

Imports System.Web
Imports System.Threading.Tasks

Public Class ApiProxy : Implements IHttpHandler, IAsyncHttpHandler

    Public Sub ProcessRequest(context As HttpContext) Implements IHttpHandler.ProcessRequest
        Throw New NotImplementedException()
    End Sub

    Public Function BeginProcessRequest(context As HttpContext, cb As AsyncCallback, extraData As Object) As IAsyncResult Implements IAsyncHttpHandler.BeginProcessRequest
        Dim task = Task.Run(Async Function()
                               Await HandleRequestAsync(context)
                           End Function)
        Return task.AsAsyncResult(cb, extraData)
    End Function

    Public Sub EndProcessRequest(result As IAsyncResult) Implements IAsyncHttpHandler.EndProcessRequest
        ' 无需额外处理
    End Sub

    Public ReadOnly Property IsReusable As Boolean Implements IHttpHandler.IsReusable
        Get
            Return False
        End Get
    End Property

    Private Async Function HandleRequestAsync(context As HttpContext) As Task
        Dim apiService = New ThirdPartyApiService()
        Dim responseData As String = String.Empty

        Select Case context.Request.HttpMethod.ToUpper()
            Case "GET"
                responseData = Await apiService.FetchDataForUIFragmentAsync()
            Case "POST"
                Dim payload = New StreamReader(context.Request.InputStream).ReadToEnd()
                responseData = Await apiService.PostDataForUIFragmentAsync(payload)
        End Select

        context.Response.ContentType = "application/json"
        context.Response.Write(responseData)
    End Function
End Class

前端AJAX调用示例

// 获取UI片段数据
fetch('/ApiProxy.ashx', {
    method: 'GET'
})
.then(response => response.json())
.then(data => {
    // 用数据渲染UI片段
    document.getElementById('fragment-container').innerHTML = buildUIFragment(data);
})
.catch(err => console.error('获取数据失败:', err));

// 提交数据
function submitData(payload) {
    fetch('/ApiProxy.ashx', {
        method: 'POST',
        headers: {
            'Content-Type': 'application/json'
        },
        body: JSON.stringify(payload)
    })
    .then(response => response.json())
    .then(result => {
        // 更新UI或处理结果
    })
    .catch(err => console.error('提交失败:', err));
}

function buildUIFragment(data) {
    // 根据返回数据构建HTML片段
    return `<div class="fragment">
                <h3>${data.title}</h3>
                <p>${data.content}</p>
            </div>`;
}

额外注意事项

  • 错误处理:在后端添加异常捕获逻辑,返回友好的错误信息给前端。
  • 权限控制:如果需要用户级认证,可在后端获取当前登录用户信息,用授权码模式向Keycloak请求令牌。
  • 日志记录:添加日志记录令牌获取、API调用的关键信息,方便排查问题。

内容的提问来源于stack exchange,提问作者Ryan Dunphy

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.19 23:35:31