2

I'm trying to upload objects form my client sq-lite database to MSSQL using Retrofit 2 & Web API 2.

The app is working without any issue if I assign null or new byte[1000] to the visit. Image, but whenever its assigned value is retrieved from the sq-lite database I get error response code 400

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

Here is my model in android:

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

This is the code how I retrieve values from database and making Visit object

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 ;
    }

And this is the used interface from retrofit service:

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

This is the activity code:

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) {
                    //....
            }
});

And this Web API 2 code

[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);
}

The below screenshot from android studio shows the retrieved content of the Visit.Image, and I am sure there is no problem with the content it self because I can read it on my android app in 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

2 Answers2

2

Well, you are posting bytes from Java to C#. The problem is Java bytes has range between -128 and 127 whilst C# bytes ranges from 0 to 255. When you post bytes from Java they got serialized into JSON string (['-128', '-30', '127']) which your WebApi controller receives them and tries to deserialize them into C# bytes (ranging from 0 to 255). Unfortunately, it fails because of the negative numbers. So you have to make it deserialize correctly.


Option 1: Use sbyte in conroller.

In WebApi, change your model to:

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

WebApi contoller will deserialize array successfully into sbyte (range: -128 to 127) and then you can convert them into byte easily (taken from SO answer):

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

Option 2: Send int[] from Java

Send your bytes as int[] so that you send the bytes ranging 0 to 255.

Change your model in Java to:

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

Convert byte[] into int[]:

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

Convert method (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;
}

Note: I'd recommend 1st option, it will be done on server-side while 2nd option might take long during converting to int[] which is on client-side.

Community
  • 1
  • 1
Orkhan Alikhanov
  • 9,122
  • 3
  • 39
  • 60
  • Thank you very much for the important information. I will test the solution and revert back with the update. – Jaffar Shehab Sep 08 '16 at 11:50
  • @JaffarShehab Thank you too, I have never written code in Java. I didn't know range of `byte` in Java is between -128 and 127. In the screenshot you provided first byte in array is `-119` which made me ponder upon. Fortunately, there was at least one negative number, since everything seemed okay except first `-119` :) – Orkhan Alikhanov Sep 08 '16 at 11:55
  • Thank you very much again for the detailed answer. it helped me a lot. – Jaffar Shehab Sep 09 '16 at 07:43
0

You can use JsonSerializer (written in 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
    }
}

and add to your Gson:

GsonBuilder()
            .registerTypeAdapter(ByteArray::class.java, ByteArrayJsonSerializer())  // to convert java Byte to C# Byte
            .create()
Francis
  • 6,788
  • 5
  • 47
  • 64