unosquare / embedio

A tiny, cross-platform, module based web server for .NET
http://unosquare.github.io/embedio
Other
1.46k stars 176 forks source link

Json returned from get have extra characters in the response #486

Closed olivier38070 closed 3 years ago

olivier38070 commented 4 years ago

I have an application written in Xamarin forms, using EMBEDIO. when another application call one web service, the web server (embed) return a json object. on the client side, the json object have extras characters then I Cannot deserialise the object. if I remove manually the extras characters from the string in the response, I can deserialise the object.

note : with embedIO 3.3.3 : I don't have the problem. with embed 3.4.3 I have the issue

-- Here is the code to start my web server

                server = new WebServer(o => o
                .WithUrlPrefix(serverUrl)
                .WithMode(HttpListenerMode.Microsoft))
                .WithLocalSessionManager()
                .WithWebApi("/api", m => m.WithController<ServerController>())
                .WithModule(new ActionModule("/", HttpVerbs.Any, ctx => ctx.SendDataAsync(new { Message = "Error" })));

            server.StateChanged += (s, e) =>
            {
                Singleton.common.Log("Web server state changed : " + e.NewState.ToString());

                Xamarin.Forms.Device.BeginInvokeOnMainThread(() =>
                {
                   // ... my code here is not important
                });
            };
            server.Start();

-- here is the GET routing

    public class ServerController : WebApiController
   {

        [Route(HttpVerbs.Get, "/get/{action}")]
        public async Task<String> Get(string action) => await GetDatas(action);
        internal async Task<String> GetDatas(String action)
        {
            await Task.Delay(0).ConfigureAwait(false);
            try
            {
                string result = getMyJSONOBject(); // do some stuff to get my serialised object in a String
                return result;  // the string is correct at this step
            }
            catch (Exception ex)
            {
                // I manage my error here. not interesting here for that issue
            }
        }

-- here is my web service code

        // get one order 
        public async void GetOrder(Order order)
        {
            try
            {
                string json = JsonConvert.SerializeObject(order);
                var httpContent = new StringContent(json, Encoding.UTF8, "application/json");
                using (var httpClient = new HttpClient())
                {
                    HttpClient client = new HttpClient();
                    client.Timeout = TimeSpan.FromSeconds(8);

                    // Do the actual request and await the response
                    var httpResponse = await client.PostAsync(serverIP + "/api/post/getOrder", httpContent);

                    // If the response contains content we want to read it!
                    if (httpResponse.Content != null)
                    {
                        var responseContent = await httpResponse.Content.ReadAsStringAsync();
                        Console.WriteLine(responseContent);
                        // here the response has extra characters at start, and end.
                    }
                }
            }
            catch (Exception ex)
            {
            }
        }

-- example of json response with extra characters

"\"{\"rooms\":[{\"tables\":[{\"num\":1            .... all my datas if here
"dehors\"}],\"versionNum\":151}\""

-- example of correct json returned by an earlier version

"{\"rooms\":[{\"tables\":                   ...... all my datas are here with correct format .......   
"}],\"versionNum\":151}"

my temporary ugly solution : remove the last characters : " remove the First characters "\"

expected : the web server should return the object without any extra character.

olivier38070 commented 4 years ago

Any idea about how i can fix that. I cannot update the nuget because of that issue. May be i can use it in another way to avoids that ? Thanks for help

rdeago commented 4 years ago

Hello @olivier38070, thanks for using EmbedIO! Please accept my apologies for taking so long to address your issue.

The extra characters are due to the fact that you are serializing data twice.

As a matter of fact, EmbedIO will automatically serialize anything your controller methods return. The default serialization method uses the JSON serializer in the SWAN library.

Is there any way you can just return the result object instead of an already-serialized string?

public class ServerController : WebApiController
{
    [Route(HttpVerbs.Get, "/get/{action}")]
    public async Task<object> Get(string action) => await GetDatas(action);

    internal async Task<object> GetDatas(string action)
    {
        await Task.Delay(0).ConfigureAwait(false);
        try
        {
            return getMyObject(); // do some stuff to get my object, without serializing it
        }
        catch (Exception ex)
        {
            // I manage my error here. not interesting here for that issue
        }
    }
}

Of course your controller method can return Task<AnyClassYouWant>. I used Task<object> just as an example.

Please let me know whether this helps solving your problem.

olivier38070 commented 4 years ago

Hello

I Will try, Thank you Best regards Olivier

Le dim. 13 sept. 2020 à 18:30, Riccardo De Agostini < notifications@github.com> a écrit :

Hello @olivier38070 https://github.com/olivier38070, thanks for using EmbedIO! Please accept my apologies for taking so long to address your issue.

The extra characters are due to the fact that you are serializing data twice.

As a matter of fact, EmbedIO will automatically serialize anything your controller methods return. The default serialization method uses the JSON serializer in the SWAN https://github.com/unosquare/swan library.

Is there any way you can just return the result object instead of an already-serialized string?

public class ServerController : WebApiController

{

[Route(HttpVerbs.Get, "/get/{action}")]

public async Task<object> Get(string action) => await GetDatas(action);

internal async Task<object> GetDatas(string action)

{

    await Task.Delay(0).ConfigureAwait(false);

    try

    {

        return getMyObject(); // do some stuff to get my object, without serializing it

    }

    catch (Exception ex)

    {

        // I manage my error here. not interesting here for that issue

    }

}

}

Of course your controller method can return Task. I used Task just as an example.

Please let me know whether this helps solving your problem.

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/unosquare/embedio/issues/486#issuecomment-691693427, or unsubscribe https://github.com/notifications/unsubscribe-auth/AB5HTREYHTP635NQKDSHRS3SFTXQ7ANCNFSM4O2IFNIA .

olivier38070 commented 3 years ago

Hello,

no your solution didn't help me. my code is generic, and return a string, with inside : string of anything, decoded properly on client side. is there a ways to avoid the EmbedIO server to serialise what it return ? ( because if I know that it is a string, I don't need the Swan serialisation ). if there is a parameter to disable Swan serialisation, I Could keep my generic GET web service .

thanks for any feedback from your side :o)

rdeago commented 3 years ago

is there a ways to avoid the EmbedIO server to serialise what it return ? ( because if I know that it is a string, I don't need the Swan serialisation ).

Yes, there is a way; I was just trying simpler methods first.

Unfortunately, the only way to change the response serialization of a Web API module is to subclass WebApiModule, as its Serializer property (inherited from WebApiModuleBase) is read-only. This is not as hard as it may seem: actually, it's almost earlier done that said.

This is a subclassed web API module that sends responses as JSON without performing any actual serialization:

using System.Threading.Tasks;
using EmbedIO;
using EmbedIO.Utilities;
using EmbedIO.WebApi;

namespace YOUR_NAMESPACE
{
    public sealed class JsonBypassWebApiModule : WebApiModule
    {
        public JsonBypassWebApiModule(string baseRoute)
            : base(baseRoute, JsonBypass)
        {
        }

        private static async Task JsonBypass(IHttpContext context, object? data)
        {
            Validate.NotNull(nameof(context), context).Response.ContentType = MimeType.Json;
            using var text = context.OpenResponseText(new UTF8Encoding(false));
            await text.WriteAsync(data?.ToString() ?? string.Empty).ConfigureAwait(false);
        }
    }
}

This is a handy extension method to keep your initialization code as fluent as before:

using EmbedIO;
using EmbedIO.WebApi;

namespace YOUR_NAMESPACE
{
    public static class WebModuleContainerExtensions
    {
        public static TContainer WithJsonBypassWebApi<TContainer>(this TContainer @this, string baseRoute, Action<WebApiModule> configure)
            where TContainer : class, IWebModuleContainer
            => @this.WithModule(new JsonBypassWebApiModule(baseRoute), configure);
    }
}

Just use WithJsonBypassWebApi instead of WithWebApi in your server initialization code.


On a side note, it would be nice if the response serializer callback was configurable without going through the hassle of subclassing. @geoperez should we add this to the v4.0 backlog?

geoperez commented 3 years ago

Sounds like a plan!

olivier38070 commented 3 years ago

thanks a lot, I will try that :o)

stale[bot] commented 3 years ago

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

rdeago commented 3 years ago

Actually you can already specify a response serializer for a WebApiModule, either with a constructor parameter or using a fluent extension method.

Shame on me for overlooking that.