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

如何用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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.22 23:06:09