Swift调用.NET C# Web API报415错误 JWT解析异常求助
1. 415 Unsupported Media Type 错误修复
你当前代码的核心问题是重复设置了两次Content-Type请求头:
- 第一次设置为
application/x-www-form-urlencoded - 第二次设置为
application/json
虽然setValue(_:forHTTPHeaderField:)逻辑上应该覆盖前一个值,但iOS部分版本的URLRequest实现中,对同一个头字段重复赋值会出现值拼接问题,最终请求头的Content-Type会变成application/x-www-form-urlencoded, application/json这种非法格式,ASP.NET Core的内容协商模块无法识别该类型,直接返回415。
另外你当前HTTP方法写为"Post"(首字母大写、其余小写),不符合HTTP规范全大写的要求,部分反向代理或服务端配置下会出现请求识别异常。
修复后的请求配置代码如下:
var request = URLRequest(url: url) // 删除多余的form-urlencoded头设置,仅保留JSON类型声明 request.setValue("application/json", forHTTPHeaderField: "Content-Type") // 增加Accept头,明确告知服务端期望返回JSON格式响应 request.setValue("application/json", forHTTPHeaderField: "Accept") // HTTP方法必须使用全大写 request.httpMethod = "POST" // 序列化请求体时移除.prettyPrinted选项,避免生成无意义的格式化缩进增加请求体积 do { request.httpBody = try JSONSerialization.data(withJSONObject: parameters, options: []) } catch { print(error.localizedDescription) return }
2. JWT令牌解码失败修复
你当前的响应模型存在两个不符合Swift规范的问题,导致无法正常解码:
- 用于JSON解码的模型必须遵守
Codable(或Decodable)协议,你的AuthenticationResponse类未遵守该协议,无法直接从二进制数据反序列化 - 接口返回的结构包含多个嵌套字段、可选值字段,你仅定义了
jwtToken一个属性,默认解码器会因字段匹配失败抛出错误 - 不建议让网络响应模型直接继承
ObservableObject,网络层应该返回纯数据模型,视图层再根据需要转换为可观察对象
修正后的响应模型代码:
import Foundation // 先定义嵌套的错误对象模型,所有可能返回null的字段都声明为可选类型 struct ApiError: Codable { let eventName: String? let errorMessage: String? let errorDate: String? let statusCode: Int? let json: String? } // 登录响应模型优先用结构体(值类型),符合Swift编码规范,自动支持Codable struct AuthenticationResponse: Codable { let id: String let firstName: String let lastName: String let username: String let playerId: Int let jwtToken: String let error: ApiError? let refreshToken: String? }
请求成功回调中的解码逻辑替换为:
do { let decoder = JSONDecoder() // 如果后端返回蛇形命名的字段,可打开下面这行配置自动转驼峰,你当前给出的示例是驼峰命名,不需要开启 // decoder.keyDecodingStrategy = .convertFromSnakeCase let authResponse = try decoder.decode(AuthenticationResponse.self, from: data) // 直接从强类型模型中取JWT即可 let jwtToken = authResponse.jwtToken print("获取到JWT令牌:\(jwtToken)") } catch { print("解码失败:\(error)") if let responseString = String(data: data, encoding: .utf8) { print("原始响应内容:\(responseString)") } }
注意:网络回调默认运行在后台线程,如果拿到令牌后需要更新UI,必须手动切换到主线程执行,逻辑和C#中WPF/MAUI需要切UI线程更新界面一致。
1. URLSession的复用注意事项
和C# HttpClient的使用习惯完全相反:Swift中强烈建议复用URLSession实例,不要每次请求都新建。
- URLSession底层会自动管理TCP连接池、TLS会话缓存、DNS缓存更新,每次新建实例反而会导致频繁握手、连接无法复用,大幅降低性能,甚至引发请求泄漏
- 通用场景直接用
URLSession.shared全局单例即可;如果需要自定义超时、请求拦截、证书校验等配置,可以创建一个全局持有的URLSession实例在整个App生命周期复用,不会出现C#中老版本HttpClient长期复用导致的DNS不更新问题,苹果底层已经自动处理了该逻辑 - URLSession不需要手动释放,ARC会自动管理内存,没有类似C#的Dispose要求
2. 简化网络请求的第三方库推荐
优先推荐Alamofire,是Swift生态最成熟的网络库,对URLSession做了全链路封装,内置JSON编解码、请求拦截、上传下载、错误统一处理等能力,和C#生态的RestSharp体验类似,可以减少60%以上的样板代码。
如果你的API已经开启Swagger,也可以直接用Swagger代码生成工具输出Swift端的请求模型和接口调用代码,不需要手写任何网络逻辑。如果不想引入第三方依赖,基于URLSession写一个通用的泛型封装,处理统一的编解码、错误逻辑,也能达到很简洁的使用效果。
- 定义数据模型优先用结构体(struct)而非类(class),结构体是值类型,没有继承的额外开销,对Codable的支持更友好,和C#中record类型的使用体验类似
- JSON解析优先用
JSONDecoder+Codable强类型解码,不要用JSONSerialization转字典再手动取值,类型安全性更高,和C#中System.Text.Json反序列化到强类型模型的逻辑一致 - Swift命名规范和C#基本一致,类型名用大驼峰、方法/变量名用小驼峰,你当前代码中
CallWebApi方法名首字母大写不符合规范,建议改为callWebApi - JWT等敏感凭证不要存在UserDefaults中,应该存入系统钥匙串,避免被轻易读取。
内容的提问来源于stack exchange,提问作者dotnetdevcsharp

