如何用C#调用BMC Remedy API以Form-Data创建带附件工单
BMC Remedy API 带附件创建工单报错排查与修复
问题背景
通过BMC Remedy API提交工单时,需要以multipart/form-data格式发送两类内容:
- 字段
entry对应文件data_entry.txt,内容为工单的JSON数据 - 字段
attach-z2AF_WIAttachment1为可选附件文件
该请求在Postman中可正常执行,但转成C#代码后,持续返回头部无效、文件类型不合法等错误。
现有代码
try { // 认证逻辑 var dict = new Dictionary<string, string>(); dict.Add("username", "applicationUsernameJonDoe"); dict.Add("password", "applicationPassowrdXPTO"); var clientLogin = new HttpClient(); var req = new HttpRequestMessage(HttpMethod.Post, Endpoint_loginITSM) { Content = new FormUrlEncodedContent(dict) }; var res = clientLogin.SendAsync(req); var body = res.GetAwaiter().GetResult().Content.ReadAsStringAsync(); // 创建工单请求 using (var client = new HttpClient()) { client.Timeout = TimeSpan.FromMinutes(10); var request = new HttpRequestMessage { RequestUri = new Uri(Endpoint_CreateITSM), Method = HttpMethod.Post }; request.Headers.Add("Authorization", body.Result.ToString()); if (!string.IsNullOrEmpty(registos.Objeto.fileName)) { registos.Objeto.Registo.z2AF_WIAttachment1 = registos.Objeto.fileName; } string json = JsonConvert.SerializeObject(new { values = registos.Objeto }); byte[] file_bytes = System.Convert.FromBase64String(registos.Objeto.fileEncoded); MemoryStream memoryStream = new MemoryStream(); using (BsonDataWriter writer = new BsonDataWriter(memoryStream)) { JsonSerializer serializer = new JsonSerializer(); serializer.Serialize(writer, registos.Objeto.Registo); } var data_entry_bytes = memoryStream.ToArray(); // 构造multipart/form-data请求 var multiForm = new MultipartFormDataContent(); ByteArrayContent data_entry_json_content = new ByteArrayContent(data_entry_bytes); data_entry_json_content.Headers.ContentType = new MediaTypeHeaderValue("application/json"); data_entry_json_content.Headers.ContentDisposition = new ContentDispositionHeaderValue("entry") { FileName = "data_entry.txt", Name = "entry", }; multiForm.Add(data_entry_json_content); ByteArrayContent z2AF_WIAttachment1_content = new ByteArrayContent(file_bytes); z2AF_WIAttachment1_content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream"); z2AF_WIAttachment1_content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attach-z2AF_WIAttachment1") { FileName = registos.Objeto.fileName, Name = "attach-z2AF_WIAttachment1", }; multiForm.Add(z2AF_WIAttachment1_content); request.Content = multiForm; var result = await client.SendAsync(request); var resBody = result.Content.ReadAsStringAsync().Result.ToString(); dynamic _resBody = JsonConvert.DeserializeObject<dynamic>(resBody); string registoID = _resBody["values"].SysRequestID; return ResponseHandler<string>.Resposta(false, "resposta api bit criar registos", registoID); } } catch (Exception e) { string classname = this.GetType().Name; CentralLibrary.Services.ErrorLoggingService.ErrorLogsForCore(classname, e, _env.WebRootPath); return ResponseHandler<string>.Resposta(true, "EXCEPTION : resposta api bit criar registos", e.Message); }
核心问题与修复方案
1. entry内容序列化错误
代码中错误使用BsonDataWriter序列化工单数据,但Postman中发送的是JSON格式的data_entry.txt。直接用JSON序列化生成字节数组即可:
// 替换原Bson序列化代码 var data_entry_json = JsonConvert.SerializeObject(registos.Objeto.Registo); var data_entry_bytes = Encoding.UTF8.GetBytes(data_entry_json);
2. ContentDisposition头部类型错误
multipart/form-data的每个字段的ContentDisposition类型必须是form-data,而非字段名。修正如下:
// 修正entry字段的ContentDisposition data_entry_json_content.Headers.ContentDisposition = new ContentDispositionHeaderValue("form-data") { Name = "entry", FileName = "data_entry.txt" }; // 修正附件字段的ContentDisposition z2AF_WIAttachment1_content.Headers.ContentDisposition = new ContentDispositionHeaderValue("form-data") { Name = "attach-z2AF_WIAttachment1", FileName = registos.Objeto.fileName };
3. 同步异步混用导致的潜在问题
认证和响应读取部分混用GetAwaiter().GetResult()和.Result,容易引发死锁,全部改用异步写法:
// 认证部分改为异步 var res = await clientLogin.SendAsync(req); var authToken = await res.Content.ReadAsStringAsync(); // 响应读取改为异步 var resBody = await result.Content.ReadAsStringAsync();
4. Authorization头部格式验证
确认登录接口返回的token是否需要前缀(如Bearer ),若API要求,需拼接后添加:
request.Headers.Add("Authorization", $"Bearer {authToken}");
5. 文件ContentType优化
避免固定使用application/octet-stream,根据文件名自动匹配MIME类型(以.NET Core为例):
var provider = new FileExtensionContentTypeProvider(); if (!provider.TryGetContentType(registos.Objeto.fileName, out var contentType)) { contentType = "application/octet-stream"; } z2AF_WIAttachment1_content.Headers.ContentType = new MediaTypeHeaderValue(contentType);
内容的提问来源于stack exchange,提问作者Helder Pereira
相关产品推荐
相关产品推荐

