【问题标题】:Web API 2 cannot read byte[] sent via Retrofit 2Web API 2 无法读取通过 Retrofit 2 发送的 byte[]
【发布时间】:2025-12-26 23:45:07
【问题描述】:

我正在尝试使用 Retrofit 2 和 Web API 2 将对象从我的客户端 sq-lite 数据库上传到 MSSQL。

如果我将nullnew byte[1000] 分配给访问,该应用程序可以正常运行。图像,但每当从 sq-lite 数据库中检索其分配的值时,我都会收到错误响应代码 400

{
  "Message": "The request is invalid.",
  "ModelState": {
    "visit.Image[0]": [
      "An error has occurred."
    ],
    "visit.Image[1]": [
      "An error has occurred."
    ]
  }
}

这是我在 android 中的模型:

public class Visit {
    public int VisitId;
    public String DealerMId;
    public byte[] Image; // read image from database (blob data type)
}

这是我如何从数据库中检索值并制作访问对象的代码

public Visit getVisitByVisitId(long visitId) {
        SQLiteDatabase db = this.getReadableDatabase();

        String selectQuery = "SELECT  * FROM " + TABLE_VISIT + " WHERE "
                + KEY_ID + " = " + visitId;
        Cursor c = db.rawQuery(selectQuery, null);

        if (c != null)
            c.moveToFirst();

        Visit visit = new Visit();

        visit.VisitId = c.getInt(c.getColumnIndex(KEY_ID));
        visit.DealerMId = c.getString(c.getColumnIndex(KEY_DEALER_MID));
        visit.Image= c.getBlob(c.getColumnIndex(KEY_PICTURE));

        return visit ;
    }

这是改造服务中使用的接口:

@POST("visits/PostVisit")
public Call<Integer> postVisit(@Body Visit visit);

这是活动代码:

Visit vistit = db.getVisitById(1) ;

// Note that : every thing working fine 
// if visit.Image = null or visit.Image = new byte[1000] or visit.Image = new byte[]{1,4,3 ..}
// but I am receiving error 400 when visit.Image contains value from database

Call<Integer> call = RetrofitService.postVisit(visit);    

call.enqueue(new Callback<Integer>() {
          @Override
          public void onResponse(Call<Integer> call, Response<Integer>response){
                    //....
            }
          @Override
          public void onFailure(Call<Integer> call, Throwable t) {
                    //....
            }
});

还有这个 Web API 2 代码

[HttpPost]
public IHttpActionResult PostVisit(Visit visit)
{

    if (!ModelState.IsValid)
    {
         return BadRequest(ModelState);
    }

    db.Visits.Add(visit);

    try
    {
        db.SaveChanges();
    }
    catch (DbUpdateException)
    {
        if (VisitExists(visit.VisitId))
        {
            return Ok(-1);
        }
        else
        {
            throw;
        }
    }
    return Ok(visit.VisitId);
}

以下来自android studio的屏幕截图显示了Visit.Image的检索内容,我确信它本身的内容没有问题,因为我可以在我的Android应用程序的ImageView中读取它。

This is a screen shot from android studio taken when I was debugging the code, it shows the Visit.Image value which is retrieved from the database

【问题讨论】:

  • But I am receiving error 400 when visit.Image contains value from database 你能贴出如何从数据库中检索值并制作Visit 对象的代码
  • 嗨,我已经编辑了问题并发布了所需的代码。
  • 仅供参考。我可以在同一个应用程序中显示从 sq-lite 到 ImageView 的图像。
  • 你确定你从c.getBlob()得到正确的字节吗?调试看看visit.Image里面有什么。
  • 我在问题末尾添加了屏幕截图和我的评论。

标签: android upload asp.net-web-api2 retrofit2


【解决方案1】:

嗯,您将 Java 中的 bytes 发布到 C#。问题是 Java 字节的范围 在 -128 和 127 之间,而 C# 字节的范围 从 0 到 255。当您从 Java 发布字节时,它们会将 序列化 转换为 JSON 字符串 (['-128', '-30', '127']),您的 WebApi 控制器会接收它们并尝试 反序列化 将它们转换为 C# 字节(范围从 0 到 255)。不幸的是,由于负数,它失败了。所以你必须让它正确反序列化。


选项 1:在控制器中使用 sbyte

在 WebApi 中,将模型更改为:

public class Visit 
{
    public int VisitId;
    public String DealerMId;
    public sbyte[] Image; // only for C#
}

WebApi 控制器会将数组成功反序列化为sbyte(范围:-128 到 127),然后您可以轻松地将它们转换为字节(取自SO answer):

byte[] imageBytes = (byte[]) (Array)myVisitModel.Image; 

选项 2:从 Java 发送 int[]

将您的字节发送为 int[],以便您发送 范围为 0 到 255 的字节。

将您的 Java 模型更改为:

public class Visit {
    public int VisitId;
    public String DealerMId;
    public int[] Image;
}

将byte[]转换成int[]:

byte[] imageBytes = c.getBlob(c.getColumnIndex(KEY_PICTURE));
visit.Image= convertToIntArray(imageBytes);

转换方法(taken from Jon Skeet's answer):

public static int[] convertToIntArray(byte[] input)
{
    int[] ret = new int[input.length];
    for (int i = 0; i < input.length; i++)
    {
        ret[i] = input[i] & 0xff; // Range 0 to 255, not -128 to 127
    }
    return ret;
}

注意:我推荐第一个选项,它将在服务器端完成,而第二个选项在转换为int[] 位于客户端

【讨论】:

  • 非常感谢您提供的重要信息。我将测试解决方案并恢复更新。
  • @JaffarShehab 也谢谢你,我从来没有用 Java 写过代码。我不知道 Java 中 byte 的范围在 -128 到 127 之间。在屏幕截图中,您提供的数组中的第一个字节是 -119,这让我深思熟虑。幸运的是,至少有一个负数,因为除了第一个 -119 之外,一切似乎都很好:)
  • 再次非常感谢您的详细解答。这对我帮助很大。
【解决方案2】:

您可以使用 JsonSerializer(用 Kotlin 编写):

class ByteArrayJsonSerializer : JsonSerializer<ByteArray> {

    override fun serialize(src: ByteArray?, typeOfSrc: Type?, context: JsonSerializationContext?): JsonElement {
        val intArray = JsonArray()
        src?.forEach { intArray.add(it.toInt() + 128) }
        return intArray
    }
}

并添加到您的 Gson:

GsonBuilder()
            .registerTypeAdapter(ByteArray::class.java, ByteArrayJsonSerializer())  // to convert java Byte to C# Byte
            .create()

【讨论】: