Return self referencing model in JSON format using Web Api 2 controller - json

I have a self referencing model called Folder and also an Entity called Content which contains the Folder Entity.
public class Folder : BaseEntity
{
public int Id { get; set; }
public string Name { get; set; }
public int? ParentId { get; set; }
public virtual Folder Parent { get; set; }
public virtual ICollection<Folder> Children { get; set; }
}
public class Content : BaseEntity
{
[Key]
public int Id { get; set; }
public string Title { get; set; }
public string HTML { get; set; }
public string Summary { get; set; }
public int XmlConfigId { get; set; }
public int FolderId { get; set; }
public virtual Folder Folder { get; set; }
}
Here is my Application Db context
public class ApplicationDbContext: DbContext
{
public DbSet<Folder> Folders { get; set; }
public DbSet<Content> Contents { get; set; }
public ApplicationDbContext() : base("ProjectDB") {
Database.SetInitializer<ApplicationDbContext>(null);
}
}
Everything works fine if i am using a razor view to display the data and also i am able to access the The Folder property that is inside the Content Entity.
The problem is when i try to display the data using Web API.
My web API
public class ContentApiController : ApiController
{
[HttpGet]
public IEnumerable<Content> GetAllContents()
{
return _unitofwork.Contents.GetAllContents();
}
}
On the Web API, the GetAllContents() function just returns the Entity models coming directly from the Folders DBSet. It is not calling the ToList() function since i want to do lazy loading. Here is the code for the GetAllContents() function.
public IEnumerable<Content> GetAllContents()
{
return ApplicationDbContext.Contents.Include(c=>c.Folder);
}
So in order for this to work i have to add.
Configuration.LazyLoadingEnabled = false;
to my applicationDbContext constructor which i really don't want.
and also
Global.asax
GlobalConfiguration.Configuration.Formatters.Remove(GlobalConfiguration.Configuration.Formatters.XmlFormatter);
WebApiConfig
JsonMediaTypeFormatter jsonFormatter = config.Formatters.OfType<JsonMediaTypeFormatter>().Single();
jsonFormatter.UseDataContractJsonSerializer = false;
jsonFormatter.SerializerSettings.Formatting = Newtonsoft.Json.Formatting.Indented;
jsonFormatter.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore;
jsonFormatter.SerializerSettings.PreserveReferencesHandling = Newtonsoft.Json.PreserveReferencesHandling.None;
Is there any way to expose the json data without out turning off Lazy loading. Thanks.

Just call ToList on your query, or, even better, ToListAsync:
[HttpGet]
public async Task<IEnumerable<Content>> GetAllContents()
{
return await _unitofwork.Contents.GetAllContents().ToListAsync;
}
Even if you enable LazyLoading, you cannot avoid to materialize your data before returning it to the client (and let the Serializer do its work).
In your MVC example, the framework itself enumerates the result in your View (I suppose), and thus you are not directly calling ToList, but in your scenario you have to materialize your Entities explicitly.
Please note that there is no performance issue in calling ToList/ToListAsync in your controller.

Related

REST API returns "bad array" instead of JSON object

I'm building REST API server in .NET core. I'm testing my code via Postman software. I have a problem with Include() method that enables me to attach navigation property data. I'm trying to get data in [HttpGet] action and objects that are being returned are wrong.
My code :
MODELS
Session model
public class Session
{
[Key]
public int IDSession { get; set; }
[Required]
public DateTime LogInTime { get; set; }
public DateTime LogOutTime { get; set; }
[Required]
public int IDUser { get; set; }
public User User { get; set; }
[Required]
public int IDMachine { get; set; }
public Machine Machine { get; set; }
}
User model
public class User
{
[Key]
public int IDUser { get; set; }
[Required]
public string Forename { get; set; }
[Required]
public string Name { get; set; }
public string AvatarPath { get; set; }
public string Email { get; set; }
public string PhoneNumber { get; set; }
public string Password { get; set; }
public User CreatedBy { get; set; }
public DateTime CreatedAt { get; set; }
public List<UserGroup> UsersGroups { get; set; }
public List<Alarm> ExecutedAlarms { get; set; }
public List<Alarm> ResetedAlarms { get; set; }
public List<AccessCard> Cards { get; set; }
public List<AccessCard> UserCardsAdded { get; set; }
public List<User> UsersAdded { get; set; }
public List<Session> Sessions { get; set; }
public List<EventsLog> Events { get; set; }
public List<Reference> References { get; set; }
public List<UserPermission> UsersPermissions { get; set; }
}
Session controller
[Produces("application/json")]
[Route("api/Sessions")]
public class SessionsController : Controller
{
private readonly DBContext _context;
#region CONSTRUCTOR
public SessionsController(DBContext context)
{
_context = context;
}
#endregion
#region HTTP GET
// GET: api/sessions
[HttpGet]
public async Task<IActionResult> GetSessions()
{
var sessions = await _context.Sessions.Include(s => s.User). ToListAsync();
if (sessions.Any())
{
return new ObjectResult(sessions);
}
else
{
return NotFound();
}
}
// GET: api/sessions/1
[HttpGet("{id}", Name = "GetSessionByID")]
public async Task<IActionResult> GetSessionByID(Int32 id)
{
var session = await _context.Sessions.Include(s => s.User).FirstOrDefaultAsync(s => s.IDSession == id);
if (session == null)
{
return NotFound();
}
else
{
return new ObjectResult(session);
}
}
#endregion
}
The idea is that User model contains List<Session> collection that he/she created. I want to be able to return users with its sessions
Of course Session model contains a single User because every session is related with a specific, single User.
Now, when I need to get all sessions objects in SessionController with GetSessions() or GetSessionsByID() I use POSTMAN [HttpGet] action like this : http://localhost:8080/api/sessions which returns me wrong data :
A session contains a user and in turn a single user is related with its sessions. It looks like it tries to return me Session object properly, includes User object but then it tries to include all sessions for that User. That's not what I want. It looks like some kind of a loop. Sessions shoud be returned with its User objects and that's it. How can I achieve that ? Am I doing some logical mistake in my models ?
Thanks !
I met also this issue recently. So, I've fixed it by adding this script in the Startup.cs file and ConfigureServices method :
services.AddMvc().AddJsonOptions(
options => options.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore
);
So, you suffix services.AddMvc() by this code who means that you have to make JSON.Net to ignore cycles finded to the nested object request. And of course having Newtonsoft.Json package installed to your project and referenced in each concerned file
For much clearer information, see this link at Related Data and Serialization section :
https://learn.microsoft.com/en-us/ef/core/querying/related-data
Hope this is helpfull for you

best way to exclude some parameters in a modelview when editing a page

I know there are a couple of options to exclude/include some parameters in a modelview like using bind or using interfaces. However I have some problems when I am trying to implement nested IEnumerable variables. For example:
public class TestViewModel()
{
public int Id { get; set; }
public IEnumerable<Organisation> KPI { get; set; }
}
public class Organisation
{
public string Id { get; set; }
public string Name {get; set;}
public DateTime StartDate {get; set;}
public IEnumerable<Regiod> CategoryValues { get; set; }
}
public class Region
{
public System.Guid Id { get; set; }
public System.Int32 RegionId { get; set; }
public int Value { get; set; }
public System.String RegionName { get; set; }
}
[HttpGet]
public ActionResult edit(int id)
{
var model = new TestViewModel();
// Do something to populate the model
view(model)
}
In the view page (razor) all fields are disabled or hidden, except field Value in Region class and StartDate in Organization. My action Code is something like:
[HttpPost]
public ActionResult edit(TestViewModel model)
{
// Do something to populate the model
}
Everything works fine, unless somebody uses for example fiddler to set other disabled or hidden values, so those fields will be updated.
What I am after is to update just enabled fields and exclude the rest even somebody tries to set a value for them.
I tried bind[Exclude and Include], but my problem is I can bind 2 values from different classes. I tried UpdateModel(model, include) and it didn't work.
Any advice would be appreciated.

Web API Json Serialization Exception & Dynamic Entities

I have a web API controller method that is returning an object which is giving the client a 500 internal server error. On the server, the output windows says "Newtonsoft.Json.JsonSerializationException". But I cannot see anything wrong with the class I am returning.. and I am sure this has historically been working. Any help would be greatly appreciated!
EDIT: Is this a problem with the web API not being able to serialize a 'dynamic entity'? The code that generates the class is here:
var id = User.Identity.GetUserId();
var user = db.Users
.Where(u => u.Id == id)
.Include(u => u.Friends)
.FirstOrDefault();
return user;
I am returning the following class;
public class User : IdentityUser
{
public User()
{
this.Friends = new List<UserFriend>();
}
public string PhoneNumber { get; set; }
public string Email { get; set; }
public List<UserFriend> Friends { get; set; }
public bool HasRegistered { get; set; }
public string LoginProvider { get; set; }
}
The 'UserFriend' class looks like this;
public class UserFriend
{
public int UserFriendId { get; set; }
public string Id { get; set; }
public string FriendUserId { get; set; }
public string FriendUserName { get; set; }
public string FriendPhoneNumber { get; set; }
}
Strangely, when I hover over the returned object on the server, the type is: {System.Data.Entity.DynamicProxies.User_7283E76A736B4DD47E89120E932CD5C04B62F84C316961F02CDAE3EEF4786504}. I am not sure what this is.. :-O
I used AutoMapper to create a DTO instead of just returning the User class. The DynamicProxies class is because the query uses lazy loading and it has not got the object yet.
After installing automapper (Install-Package AutoMapper);
Mapper.CreateMap<User, UserDto>();
UserDto dto = Mapper.DynamicMap<UserDto>(user);
Then return the dto.

How to read this Json to controller object? Kendo UI grid server filtering

I am trying to filter Kendo UI grid server side filter. The developer tools show this in query string
/Home/GetUsmMessage?{"filter":{"logic":"and","filters" [{"field":"MessageId","operator":"eq","value":1}]},"group":[]} GET 200 application/json
I created a object structure so that I read the structure to object
public ActionResult GetUsmMessage(FilterContainer filter)
{
//Code to read the filter container
return Json(jsonData, JsonRequestBehavior.AllowGet);
}
Object structure for filter container:
public class FilterContainer
{
public List<FilterDescription> filters { get; set; }
public string logic { get; set; }
}
public class FilterDescription
{
public string #operator { get; set; }
public string field { get; set; }
public string value { get; set; }
public List<FilterDescription> filters { get; set; }
public string logic { get; set; }
}
It still gives me a null object when I debug controller function. Please help
Got the answer...I forgot to add type of request as Http post ....
In case of WebApi controller, you could use [FromUri] attributes and GET verb:
public HttpResponseMessage Get(
[FromUri]IEnumerable<SortParameter> sort,
[FromUri]FilterContainer filter,
int take = 10, int skip = 0)

Code First Object not properly instantiating

I have a class department inheriting from activeentity
public class ActiveEntity : Entity, IActive
{
public ActiveEntity()
{
IsActive = true;
}
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public Guid Id { get; set; }
public bool IsActive { get; set; }
[Timestamp, ScaffoldColumn(false), DatabaseGenerated(System.ComponentModel.DataAnnotations.DatabaseGeneratedOption.Computed)]
public Byte[] Timestamp { get; set; }
[ScaffoldColumn(false)]
public string CreationUserId { get; set; }
[ScaffoldColumn(false)]
public string LastModifiedUserId { get; set; }
}
public class Department:ActiveEntity
{
public Department()
{
this.Address = new DepartmentAddress();
}
[StringLength(9),MinLength(9),MaxLength(9)]
public string Name { get; set; }
public Guid ManagerId { get; set; }
[UIHint("AjaxDropdown")]
public User Manager { get; set; }
public Guid? AddressId { get; set; }
public DepartmentAddress Address { get; set; }
public ICollection<OverheadRate> OverheadRates { get; set; }
}
I am just using annotations no Fluent API. The data saves to the data Sql Server 2008 just fine however the address object never gets instantiated, even though I have the context use the include
return c.Set<Department>().Include(d => d.Address).Include(d => d.Manager).Where(predicate);
The data is returned I run sql profiler and then run the query it returns the correct data.
Any thoughts or suggestions?
Remove instantiating the address (this.Address = new DepartmentAddress();) in the Department constructor. Instantiating navigation references in the default constructor is evil and has nasty side effects like these:
What would cause the Entity Framework to save an unloaded (but lazy loadable) reference over existing data?
EF 4.1 Code First: Why is EF not setting this navigation property?