1

I'm kind of new when it comes to api's and json serialization. I'm encountering a weird output. It seems that my json output is being truncated or wrongly formatted?

(I'm using a web api with .net core 2.0 and entity framework core 2.0 preview2)

The following error occurs in postman:

Expected ',' instead of ''

But when checking the raw data I see that not everything is being send:

 {
    "id": 2,
    "username": "c8bee98",
    "firstname": "Firstname73",
    "lastname": "Lastname73",
    "creationDate": "2017-07-02T23:47:16.204588",
    "activityDate": "2017-07-03T14:02:58.6982234+02:00",
    "profilePictureURL": "ProfilePictureURL73",
    "gender": 2,
    "location": "POINT(25.1400680863951
    -25.0786780636193)",
    "appsetting": null,
    "email": "Email73",
    "isHidden": false,
    "isBanned": false,
    "feedback": [{
            "id": 19,
            "rating": 1,
            "feedbackType": 0,
            "message": "Message1",
            "creationDate": "2017-07-02T23:47:18.2287331",
            "userId": 2

My controller looks like this:

    UserManager userManager = new UserManager();

    [HttpGet]
    public async Task<User> GetById(int id)
    {
        try
        {
            return await userManager.GetByIdAsync(id);
        }
        catch (Exception ex)
        {

            throw ex;
        }
    }

My model looks like this:

public class User
{
    [DisplayName("Id")]
    public int Id { get; set; }


    private string username = Guid.NewGuid().ToString("N");

    [Required(ErrorMessage = "Username is required")]
    [DisplayName("Username")]
    [StringLength(32)]
    public string Username
    {
        get {
            string shortusername = username;
            return shortusername.Remove(0,25);
        }
    }
    public string ResetUsername()
    {
        username = Guid.NewGuid().ToString("N");
        return username;
    }

    [Required(ErrorMessage = "Firstname is required")]
    [StringLength(50, ErrorMessage = "Firstname can only be 50 characters long")]
    [DisplayName("Firstname")]
    public string Firstname { get; set; }

    [Required(ErrorMessage = "Lastname is required")]
    [StringLength(50, ErrorMessage = "Lastname can only be 50 characters long")]
    [DisplayName("Lastname")]
    public string Lastname { get; set; }

    [Required(ErrorMessage = "Creation date is required")]
    [DisplayName("Creation Date")]
    [DataType(DataType.Date)]
    [Column(TypeName = "datetime2")]
    [DisplayFormat(ApplyFormatInEditMode = true, DataFormatString = "{0:yyyy-MM-dd}")]
    public DateTime CreationDate { get; set; } = DateTime.Now;

    [Required(ErrorMessage = "Last activity date could not be registered")]
    [DisplayName("Last Activity Date")]
    [DataType(DataType.Date)]
    [Column(TypeName = "datetime2")]
    [DisplayFormat(ApplyFormatInEditMode = true, DataFormatString = "{0:yyyy-MM-dd}")]
    private DateTime lastActivityDate;

    public DateTime ActivityDate
    {
        get { return lastActivityDate; }
        set { lastActivityDate = DateTime.Now; }
    }

    [DataType(DataType.ImageUrl)]
    [StringLength(2000, ErrorMessage = "That link is to long!")]
    [DisplayName("Profile Picture")]
    public string ProfilePictureURL { get; set; }

    [Required]
    [DisplayName("Gender")]
    public Gender Gender { get; set; } = Gender.Unknown;

    private string location = "POINT (0 0)";

    [Required]
    [DataType("geography")]
    [DisplayName("Location")]
    public string Location
    {
        get { return location; }
    }

    public string SetLocation(double latitude, double longitude)
    {
        location = String.Format("POINT({0} {1})", longitude, latitude).Replace(',', '.');
        return location;
    }
    public double GetLatitude()
    {
        return double.Parse(location.Split('(', ')')[0].Split(' ')[0]);
    }
    public double GetLongitude()
    {
        return double.Parse(location.Split('(', ')')[0].Split(' ')[1]);
    }

    [DisplayName("App Settings")]
    public AppSetting Appsetting { get; set; }

    [DisplayName("Email")]
    [DataType(DataType.EmailAddress, ErrorMessage = "Invalid email" )]
    [StringLength(320)]
    public string Email { get; set; }
    [Required]
    [DisplayName("Hidden")]
    public bool IsHidden { get; set; } = false;

    [Required]
    [DisplayName("Banned")]
    public bool IsBanned { get; set; } = false;

    //User can have multiple feedbacks
    public IEnumerable<Feedback> Feedback { get; set; }

    //User can have multiple reported users
    public IEnumerable<Reported> ReportedUsers { get; set; }

    //User can have multiple blocked users
    public IEnumerable<Blocked> BlockedUsers { get; set; }

    //User can have multiple connections
    public IEnumerable<Connection> Connections { get; set; }

    //User can have multiple categories
    public IEnumerable<UserCategory> UserCategory { get; set; }

    //User can have multiple roles
    public IEnumerable<UserRole> UserRole { get; set; }

    //User can have multiple messages
    public IEnumerable<Message> Messages { get; set; }

    //User can watch multiple advertisements
    public IEnumerable<Advertisement> Advertisement { get; set; }

    //User can watch multiple advertisements
    public IEnumerable<UserAdvertisement> WatchedAdvertisement { get; set; }

    //User can have multiple providers
    public IEnumerable<UserProvider> UserProvider { get; set; }
}

Any suggestions?

EDIT:

My feedback collection has 2 items (2 feedbacks).

Only 1 is shown with all its properties.

Feedback Model:

public class Feedback
{
    [DisplayName("Id")]
    public int Id { get; set; }

    [Required(ErrorMessage = "A rating is required")]
    [DisplayName("Rating")]
    public int Rating { get; set; }

    [Required(ErrorMessage = "A feedbacksection is required")]
    [DisplayName("Section")]
    public FeedbackType FeedbackType { get; set; }

    [DisplayName("Message")]
    //[DataType("nvarchar(500)")]
    [StringLength(500, ErrorMessage = "That message is to long!")]
    public string Message { get; set; }

    [Required(ErrorMessage = "Creation date is required")]
    [DisplayName("Creation Date")]
    [DataType(DataType.Date)]
    [Column(TypeName = "datetime2")]
    [DisplayFormat(ApplyFormatInEditMode = true, DataFormatString = "{0:yyyy-MM-dd}")]
    public DateTime CreationDate { get; set; } = DateTime.Now;

    [Required]
    [DisplayName("User")]
    public int UserId { get; set; }
    public User User { get; set; }
}
Hades
  • 865
  • 2
  • 11
  • 28
  • 1
    What's not being sent? The collections? – Camilo Terevinto Jul 03 '17 at 12:22
  • This usually happens when there is an error serializing the data. You got any exception on the console/debug window? P.S. using EF models as result of Actions is a **pretty bad idea**, especially with two way navigation properties (circular references) – Tseng Jul 03 '17 at 12:26
  • @CamiloTerevinto yes, the collection feedback is not being sent completly. – Hades Jul 03 '17 at 12:28
  • @Tseng, no exception, the collection feedback has 2 items, but only showing 1. – Hades Jul 03 '17 at 12:28
  • 3
    Yea but does Feedback have a reference back to the user? If yes, then you have a circular dependency. User -> Feedback -> User -> Feedback -> User – Tseng Jul 03 '17 at 12:30
  • You could also try to manually serialize it (http://www.newtonsoft.com/json/help/html/SerializingJSON.htm) via `string output = JsonConvert.SerializeObject(result)` and see if you get an exception there. IIRC ASP.NET Core will swallow any exceptions happened after the action is called – Tseng Jul 03 '17 at 12:33
  • @Tseng that must be it! but its needed for the entity framework core relationship. (One to many) One user has multiple feedbacks – Hades Jul 03 '17 at 12:34
  • @Tseng the OP should first (easier) try to nullify the User property in the Feedback objects (since EF will always populate it). But Eli, you should never return EF entities to a client – Camilo Terevinto Jul 03 '17 at 12:34
  • 1
    That's why I said: don't use EF Entities as return result. Create ViewModels/Dto without self-referencing properties, then map it (i.e. using AutoMapper, but only for EF Entity -> Dto. Never from Dto -> EF Entity) – Tseng Jul 03 '17 at 12:38
  • Maybe not [Json.Net seems to deal with circular references](https://stackoverflow.com/questions/12584986/how-to-fix-circular-reference-error-when-dealing-with-json). I'd still say don't use Core unless you have a reason to. Use 4.6.1, that's the latest mature implmentation of .Net. – Liam Jul 03 '17 at 12:41
  • *when checking the raw data*, how are you checking this raw data? – Liam Jul 03 '17 at 12:41
  • @Tseng : JsonConvert.SerializeObject returns an error. This instance contains state that cannot be serialized and deserialized on this platform. – Hades Jul 03 '17 at 12:44
  • 1
    @Liam I'm using Core because I'm making a Multiservices API Gateway on Amazon AWS (basicly my api becomes a lambda function) – Hades Jul 03 '17 at 12:45
  • 1
    Post the whole exception including the stack maybe this gives more information about the issue – Tseng Jul 03 '17 at 12:45
  • Eveyone is just guessing now, the OP needs to provide a [Minimal, Complete, and Verifiable example](https://stackoverflow.com/help/mcve) – Liam Jul 03 '17 at 12:46
  • This question is becoming a mess. – Liam Jul 03 '17 at 12:50
  • @Tseng, I've added the stacktrace when using SerializeObject. – Hades Jul 03 '17 at 12:50
  • @Liam am I not providing enough information / code ? – Hades Jul 03 '17 at 12:52
  • Ugh, why is this being downvoted? Its a perfectly reasonable question. Stackoverflow... -_- – Hades Jul 03 '17 at 12:54
  • If anything too much. Like I said please create a [**Minimal**, Complete, and Verifiable example](https://stackoverflow.com/help/mcve) of your problem. Drip feeding little bit of information isn't helping anyone here. – Liam Jul 03 '17 at 12:54
  • @Tseng solved it ( see answer ) Thanks! – Hades Jul 03 '17 at 13:39
  • @Liam solved it ( see answer ) Thanks! – Hades Jul 03 '17 at 13:39

1 Answers1

4

Even though there is no selfrefencing exception, it is occuring.

The following sample solved my problem.

(Thank you @Tseng & @Liam for pushing me in the right direction)

[HttpGet]
public async Task<string> GetById(int id)
{
    try
    {
        User user = await userManager.GetByIdAsync(id);
        JsonSerializerSettings jsonSerializerSettings = new JsonSerializerSettings
        {
            ReferenceLoopHandling = ReferenceLoopHandling.Ignore
        };

        return JsonConvert.SerializeObject(user, jsonSerializerSettings); ;
    }
    catch (Exception ex)
    {

        throw ex;
    }
}

If you want it globally:

public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc().AddJsonOptions(options => 
     options.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore);
}

Output:

{
"Id": 2,
"Username": "c8bee98",
"Firstname": "Firstname73",
"Lastname": "Lastname73",
"CreationDate": "2017-07-02T23:47:16.204588",
"ActivityDate": "2017-07-03T15:25:45.294169+02:00",
"ProfilePictureURL": "ProfilePictureURL73",
"Gender": 2,
"Location": "POINT(25.1400680863951 -25.0786780636193)",
"Appsetting": null,
"Email": "Email73",
"IsHidden": false,
"IsBanned": false,
"Feedback": [
    {
        "Id": 19,
        "Rating": 1,
        "FeedbackType": 0,
        "Message": "Message1",
        "CreationDate": "2017-07-02T23:47:18.2287331",
        "UserId": 2
    },
    {
        "Id": 20,
        "Rating": 1,
        "FeedbackType": 0,
        "Message": "Message1",
        "CreationDate": "2017-07-02T23:47:18.2287445",
        "UserId": 2
    }
],
"ReportedUsers": null,
"BlockedUsers": null,
"Connections": null,
"UserCategory": null,
"UserRole": null,
"Messages": null,
"Advertisement": null,
"WatchedAdvertisement": null,
"UserProvider": null
}
adiga
  • 34,372
  • 9
  • 61
  • 83
Hades
  • 865
  • 2
  • 11
  • 28
  • 3
    If that solves it, then it was circular reference issue. You can do this in Startup.cs class for all json responses via `services.AddMvc().AddJsonOptions(options => options.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore);`, then you don't have to explicitly call the serializer. But it be aware, that the double referenced will be removed from the result. So if you have Order -> User -> Orders, the "User.Orders" element will not include the first order (the root). Best to use DTOs w/o back references and map to them from EF Entities – Tseng Jul 03 '17 at 13:44
  • I'm not sure how much I helped unless creating a minimal example helped you sort it out. Anyway, glad you got there. – Liam Jul 03 '17 at 14:00