Search code examples
c#mongodbasp.net-web-apideserializationpoco

Infinitely method execution (Mongodb Deserialization) in ASP.NET WebAPI


In short: methods in default Console Application project works fine, but in the another project(asp.net WebAPI) the same methods doesn't works. I've a two mongoDB collections, that presented below. Sample document of subjects collection(JSON):

{
        "_id" : ObjectId("5b9a2637635d16b2a2c5c562"),
        "userId" : ObjectId("5b9a23d1a54d26b98f6acf34"),
        "name" : "someName",
        "notes" : [
                {
                        "date" : ISODate("2012-11-20T05:05:15.229Z"),
                        "title" : "someTitle",
                        "body" : "Note body - long teeeeeeext",
                        "files" : [ ]
                }
        ]
}

Sample document of users collection:

{
        "_id" : ObjectId("5b9a23d1a54d26b98f6acf34"),
        "loginName" : "someName",
        "pass" : "hashedpassword"
}

I wrote a few classes (POCO Representation):

public class Subject
{
    [BsonElement("_id")]
    public ObjectId Id { get; set; }

    [BsonElement("name")]
    public string Name { get; set; }

    [BsonElement("notes")]
    public List<Notes> Notes { get; set; }

    [BsonElement("userId")]
    public ObjectId UserId { get; set; }
}

public class Notes
{
    [BsonElement("date")]
    [BsonRepresentation(BsonType.DateTime)]
    public DateTime Date { get; set; }

    [BsonElement("title")]
    public string Title { get; set; }

    [BsonElement("body")]
    public string Body { get; set; }

    [BsonElement("files")]
    public List<ObjectId> Files { get; set; }
}

public class User
{
    [BsonElement("_id")]
    public ObjectId Id { get; set; }

    [BsonElement("loginName")]
    public string LoginName  { get; set; }

    [BsonElement("pass")]
    public string HashedPass { get; set; }
}

And classes for DB manipulation:

public class MongoDataModel
{
   /// <summary>
   /// Singleton
   /// </summary>
    public static MongoDataModel Instance { get; set; }

   public IMongoClient MongoClient { get; set; }

   public IMongoDatabase CurrentMongoDB { get; set; }

   public string CurrentDB { get; set; }

   public async Task<List<User>> GetUsers(IMongoDatabase db)
   {
       List<User> users = await db.GetCollection<User>(MongoSettings.Instanse.UsersCollection)
           .Find(new BsonDocument())
           .ToListAsync();

       return users;
   }

   public async Task<List<Subject>> GetSubjects(IMongoDatabase db)
   {
       List<Subject> subjects = await db.GetCollection<Subject>(MongoSettings.Instanse.SubjectCollection)
           .Find(new BsonDocument())
           .ToListAsync();

       return subjects;
   }

   public MongoDataModel()
   {
       CurrentDB = "reminder1";
       MongoClient = new MongoClient(MongoSettings.Instanse.ClientSettings);
       CurrentMongoDB = MongoClient.GetDatabase(CurrentDB);
   }

   static MongoDataModel()
   {
       Instance = new MongoDataModel();
   }
}

public class MongoSettings
{
    public MongoClientSettings ClientSettings { get; set; }

    /// <summary>
    /// Singleton
    /// </summary>
    public static MongoSettings Instanse { get; set; }

    public string UsersCollection { get; set; }

    public string SubjectCollection { get; set; }

    private string Host { get; set; }

    private int Port { get; set; }

    static MongoSettings()
    {
        Instanse = new MongoSettings()
        {
            Host = "localhost",
            Port = 27017,

            UsersCollection = "users",
            SubjectCollection = "subjects",
        };

        Instanse.ClientSettings = new MongoClientSettings
        {
            Server = new MongoServerAddress(Instanse.Host, Instanse.Port)
        };
    }
}

Console app code, works ok:

class Program
{
    static void Main(string[] args)
    {
        MongoDataModel.Instance
            .GetUsers(MongoDataModel.Instance.CurrentMongoDB)
            .GetAwaiter()
            .GetResult().ForEach(usr => {
                Console.WriteLine("Test user values\nUser id: {0}\nlogin:{1}\nHashed pass:{2}\n",
                    usr.Id, usr.LoginName, usr.HashedPass);
        });

        MongoDataModel.Instance
            .GetSubjects(MongoDataModel.Instance.CurrentMongoDB)
            .GetAwaiter()
            .GetResult()
            .ForEach(subj => {
                Console.WriteLine("Test subject values\nid:{0}\nName:{1}\nNumber of notes:{2}\nNotes:",
                    subj.Id, subj.Name, subj.Notes.Count);

                    subj.Notes.ForEach(note => {
                        Console.WriteLine(" Note Title:{0}\n Note Body:{1}\n NoteDate:{2}",
                            note.Title, note.Body, note.Date.ToString());
                    });
        });

        Console.ReadLine();
    }
}

Console output (code above works ok):

Test user values
User id: 5b9a23d1a54d26b98f6acf34
login:someName
Hashed pass:hashedpassword

Test subject values
id:5b9a2637635d16b2a2c5c562
Name:someName
Number of notes:1
Notes:
 Note Title:someTitle
 Note Body:Note body - long teeeeeeext
 NoteDate:11/20/2012 5:05:15 AM

I set JSON formatter as default formatter. ASP.NET WebAPI code:

public class ValuesController : ApiController
{
    // GET api/values
    public IEnumerable<User> Get()
    {
        List<User> users = MongoDataModel.Instance
            .GetUsers(MongoDataModel.Instance.CurrentMongoDB)
            .GetAwaiter()
            .GetResult();

        TestMethod();

        return users;
    }

    private void TestMethod() { }
}

public static class WebApiConfig
{
    public static void Register(HttpConfiguration config)
    {
        // Web API configuration and services

        // Web API routes
        config.MapHttpAttributeRoutes();

        config.Formatters.JsonFormatter.SupportedMediaTypes
              .Add(new MediaTypeHeaderValue("text/html"));

        config.Routes.MapHttpRoute(
            name: "DefaultApi",
            routeTemplate: "api/{controller}/{id}",
            defaults: new { id = RouteParameter.Optional }
        );
    }
}

In the Chrome(IIS) doesn't get that values, because method doesn't return the value. First breakpoint works, therefore method executes, but second breakpoint executes never:Chrome debug window Breakpoints

I have no idea what's going on. I'm so confused by that strange code behavior. What's wrong with my code? Please help. Thank you in advance!


Solution

  • Is it deadlocking? If so its beacuse you are mixing async and non-async code and the deadlock is occuring when trying to recapture the synchronization context. Make the code async all the way.

    public async Task<IEnumerable<User>> Get()
    {
        List<User> users = await MongoDataModel.Instance.GetUsers(MongoDataModel.Instance.CurrentMongoDB);
    
        TestMethod();
    
        return users;
    }
    

    • See the marked duplicate for details
    • You should use the same naming convention as microsoft, that is anything that returns Task or Task<T> should be suffixed with Async so rename GetUsers to GetUsersAsync.
    • Use async through out the whole call stack if you are going to use it.