Net Core 2.1 Controller truncates Json while the json has no reference loops - asp.net-core

I'm struggling with net core returning truncated response. I have already defined no reference loop in my startup services, and also tried to set compatibility version for the version i'm currently using 2.1 as follows:
services.AddMvc()
.SetCompatibilityVersion(CompatibilityVersion.Version_2_1)
.AddJsonOptions(x => x.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore);
I also tried to serialize the array using JsonConvert and it did not throw any reference loop exception. Here's the action in the controller and the serializedArray text:
public IActionResult GetProductItems(int productId)
{
try
{
var productItems = _productsMethods.GetProductItems(productId);
// for testing the object for ref loops
string serialized = Newtonsoft.Json.JsonConvert.SerializeObject(productItems);
return Ok(productItems);
}
catch (ClientException ex)
{
return BadRequest(new { message = ex.Message });
}
catch (Exception ex)
{
return StatusCode(500, new { message = ex.Message });
}
}
// serialized string
//[{"ID":2,"ProductId":6,"ItemId":4,"Product":null,"Item":null,"Orders":[]},{"ID":3,"ProductId":":6,"ItemId":1,"Product":null,"Item":null,"Orders":[]},{"ID":5,"ProductId":":6,"ItemId":2,"Product":null,"Item":null,"Orders":[]}]
Here's the actual response
[{"id":2,"productId":6,"itemId":4,"product":null,"item":null,"orders":
Method:
public List<ProductItem> GetProductItems(int productId)
{
IQueryable<DataSets.ProductItem> query = db.ProductItems
.AsNoTracking()
.Include(k => k.Orders)
.Where(k => k.ProductId == productId);
// result truncated (when array orders is empty)
//return query.Select(_mapper.Map<ProductItem>).ToList();
// without automapper, also truncated
//return query.Select(k => new ProductItem()
//{
// ID = k.ID,
// ItemId = k.ItemId,
// ProductId = k.ProductId,
// Orders = k.Orders.Select(a => new Order() { ID = a.ID })
// .ToList()
//}).ToList();
// WORKS, not getting truncated
// order not included
return query.Select(k => new ProductItem()
{
ID = k.ID,
ItemId = k.ItemId,
ProductId = k.ProductId,
}).ToList();
}
Entities (renamed and removed props for simplification):
public class Product
{
public int ID { get; set; }
// some props
public string UserId { get; set; }
public User User { get; set; }
public List<ProductItem> Items { get; set; }
}
public class ProductItem
{
public int ID { get; set; }
// some props
public int ProductId { get; set; }
public int ItemId { get; set; }
public Product Product { get; set; }
public Item Item { get; set; }
public List<Order> Orders { get; set; }
}
public class Order
{
public int ID { get; set; }
// some props
public int ItemId { get; set; }
public ProductItemOrder Item { get; set; }
}
Since there is no reference loop in orders and also the reference loop is ignored. Why is this still truncating?

I think the problem is public Product Product { get; set; } part here. You should define your Product entity virtually.
Here is an example below,
public class Blog
{
public int BlogId { get; set; }
public string Name { get; set; }
public string Url { get; set; }
public string Tags { get; set; }
public virtual ICollection<Post> Posts { get; set; }
}
Here is source.

For someone who might have this issue in the future. While I think the API should throw that error instead of just truncating the response.
I had 2 properties with the same letters but different letter case IPAddress and IpAddress.
SerializeObject alone wasn't throwing an exception, then I did this (CamelCase Resolver) to point out the issue:
Newtonsoft.Json.JsonConvert.DefaultSettings = () => new Newtonsoft.Json.JsonSerializerSettings
{
NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore,
ContractResolver = new Newtonsoft.Json.Serialization.CamelCasePropertyNamesContractResolver()
};
string serialized = Newtonsoft.Json.JsonConvert.SerializeObject(productItems);
So it threw: A member with the name 'ipAddress' already exists on ...

Related

Convert Generic API Response in Blazor

I'm developing a Blazor WASM project and I'm stuck in this point.
I'm using a DataAccess Service to make the requests to EndPoints;
The endpoints return a ResultList, that is a Generic Object that needs to be parsed in Client side. The object definition:
public class ResultList
{
public ResultList(List<object> resultados, string codigoErro = null, string mensagemErro = null)
{
this.Resultados = resultados;
this.CodigoErro = codigoErro;
this.MensagemErro = mensagemErro;
}
public string MensagemErro { get; set; }
public List<object> Resultados { get; set; }
public string CodigoErro { get; set; }
}
In the client side, I receive the same type:
public async Task<ResultList> GetEmpresas()
{
try
{
ResultList Result = await _httpClient.GetFromJsonAsync<ResultList>("api/EmpCadBasico/GetEmpresas");
return Result;
}
catch (Exception ex)
{
return new ResultList(null, null, ex.Message);
}
}
The problem is: I can't convert the List<Object> to other type like List<Empresa>.
The C# compilation doesn't notify bug, but in execution time, it happens.
I tried Serialize and Deserialize, and it doesn't work too:
public async Task GetEmpresas()
{
ResultList Resultado = await _dataAccess.GetEmpresas();
if (await RetornoOk(Resultado))
{
string x = JsonSerializer.Serialize(Resultado.Resultados); // Here, that's fine.
List<Empresa> y = JsonSerializer.Deserialize<List<Empresa>>(x); // Here, it finds the objects, but all of them with null values.
}
}
The X value: '[{"id":1,"nomeEmpresa":"Alamo","cnpj":"00072619000101","dataCadastro":"2020-01-01T00:00:00","colaborador":[],"marca":[]}]'
The Y value: Y value after Deserialization
According to the json return data you provided, I did the following restoration and successfully returned the data, you can refer to it.
Model:
public class TestModel
{
public int id { get; set; }
public string nomeEmpresa { get; set; }
public string cnpj { get; set; }
public string dataCadastro { get; set; }
public List<colaborador> colaborador { get; set; }
public List<marca> marca { get; set; }
}
public class colaborador
{
public int id { get; set; }
public string test { get; set; }
}
public class marca
{
public int id { get; set; }
public string test { get; set; }
}
Then I gave values to individual attributes, and the results are as follows:

Can i add a parent record and a child record using the same _context.SaveChangesAsync()

I have the following 2 Parent/Child objects:-
public Submission()
{
SubmissionQuestionSubmission = new HashSet<SubmissionQuestionSubmission>();
}
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Npi { get; set; }
public bool Independent { get; set; }
public string Comment { get; set; }
public virtual ICollection<SubmissionQuestionSubmission> SubmissionQuestionSubmission { get; set; }
}
public partial class SubmissionQuestionSubmission
{
public int SubmissionQuestionId { get; set; }
public int SubmissionId { get; set; }
public string Answer { get; set; }
public virtual Submission Submission { get; set; }
}
and i created the following view model:-
public class SubmissionCreate
{
public Submission Submission {set; get;}
public IList<SubmissionQuestion> SubmissionQuestion { set; get; }
public IList<SubmissionQuestionSubmission> SubmissionQuestionSubmission { set; get; }
}
where i have the following action method to add a parent record (submission) and a child record (SubmissionQuestionSubmission ), but to do so, i have to issue 2 save requests to the database, one to save the parent and get its ID, while the other to save the child record and assign it the parent ID, as follow:-
public async Task<IActionResult> Create(SubmissionCreate sc)//Bind("Id,FirstName,LastName,Npi,Independent,Comment")]
{
if (ModelState.IsValid)
{
var newsubmission = _context.Submission.Add(sc.Submission);
await _context.SaveChangesAsync();
foreach (var v in sc.SubmissionQuestionSubmission)
{
v.SubmissionId = sc.Submission.Id;
_context.SubmissionQuestionSubmission.Add(v);
}
await _context.SaveChangesAsync();
TempData["message"] = "Thank You.. Your request has been submitted...";
return View("Confirmation");
}
return View(sc);
}
so my question is if i can do the above job, using one save statement instead of 2?
You don't need use two SaveChanges. You can assign newsubmission into Submission property instead of v.SubmissionId = sc.Submission.Id;.
In this case Id and ForeignKey created automatically by EF Core
var newsubmission = _context.Submission.Add(sc.Submission);
foreach (var v in sc.SubmissionQuestionSubmission)
{
v.Submission = newsubmission;
_context.SubmissionQuestionSubmission.Add(v);
}
await _context.SaveChangesAsync();
Another way
sc.Submission.SubmissionQuestionSubmission = new List<SubmissionQuestionSubmission>();
foreach (var v in sc.SubmissionQuestionSubmission)
{
sc.Submission.SubmissionQuestionSubmission.Add(v)
}
_context.Submission.Add(sc.Submission);
await _context.SaveChangesAsync();

ASP.NET MVC query lambda expression

Hello I have problem in one query. Why it's always return no value.
public List<UserDetail> userSearchModel(UserSearchModel searchModel)
{
string userid = User.Identity.GetUserId();
var user = _dbContext.UserDetails.Where(x => x.Id == userid);
var result = _dbContext.UserDetails.Except(user).ToList().AsQueryable();
if (searchModel != null)
{
if (searchModel.LanguageId.Count() != 0)
{
List<UserDetailLanguage> usrDetails = new List<UserDetailLanguage>();
foreach (var item in searchModel.LanguageId)
{
var details = _dbContext.UserDetailLanguages.Where(x => x.LanguageId == item).ToList();
foreach (var item2 in details)
{
usrDetails.Add(item2);
}
}
result = result.Where(x => x.UserDetailLanguages == usrDetails);
}
}
return result.ToList();
}
I want to get results which are the same in usrDetails list and in result.UserDetailLanguages.
In result.UserDetailLanguages I have record equals to record in usrDetails but this not want retrieve.
Here is my model:
public class UserDetail
{
public UserDetail()
{
this.UserDetailLanguages = new HashSet<UserDetailLanguage>();
}
[Key, ForeignKey("User")]
public string Id { get; set; }
public DateTime Birthday { get; set; }
public string Sex { get; set; }
public string Country { get; set; }
public string About { get; set; }
[NotMapped]
public int Age { get { return DateTime.Now.Year - Birthday.Year; } }
public virtual ApplicationUser User { get; set; }
public virtual ICollection<UserDetailLanguage> UserDetailLanguages { get; set; }
}
public class UserDetailLanguage
{
public Int32 Id { get; set; }
public virtual UserDetail UserDetail { get; set; }
public string UserDetailId { get; set; }
public virtual Language Language { get; set; }
public Int32 LanguageId { get; set; }
public Boolean IsKnown { get; set; }
public static implicit operator List<object>(UserDetailLanguage v)
{
throw new NotImplementedException();
}
}
public class Language
{
public Language()
{
this.UserDetailLanguages = new HashSet<UserDetailLanguage>();
}
public int Id { get; set; }
public string Value { get; set; }
public string Name { get; set; }
public virtual ICollection<UserDetailLanguage> UserDetailLanguages { get; set; }
}
What I'm doing wrong?
If you want to see if your value is in a list you use the Contains function of the list -- like this:
result = result.Where(x => usrDetails.Contains(x.UserDetailLanguage));
If you want to see if there are any items in both lists you can use intersection like this:
result = result.Where(x => usrDetails.Intersect(x.UserDetailLanguage).Count() > 0);
Looks like you are checking equality between lists in following code
result = result.Where(x => x.UserDetailLanguages == usrDetails);
This might not work, to check equality for lists you can use something like
Enumerable.SequenceEqual(FirstList.OrderBy(fList => fList),
SecondList.OrderBy(sList => sList))

How to bind custom model class in mvc

I am new in MVC. I am working on a project where i have created a model class and also context class which is working good if i view the record in normal view.
but if i try to get the data in group by "Series_Name" and bind it into same model class it gives error. here is my code
Here is Model class and DBContextClass
[Table("tblvideo")]
public class TVSerial
{
[Key]
public Int64 Video_ID { get; set; }
public string Series_Name { get; set; }
public string Season_No { get; set; }
public string Episode_No { get; set; }
public string Episode_Name { get; set; }
public string Time_Duration { get; set; }
public string File_Url_480p { get; set; }
public string File_Url_720p { get; set; }
public string Description { get; set; }
public bool Is_Active { get; set; }
public string Image_Url_Small { get; set; }
public string Image_Url_Big { get; set; }
}
public class TvSerialContext : DbContext
{
public DbSet<TVSerial> TvSerials { get; set; }
}
Here is controller class:
public class TvSerialController : Controller
{
public ActionResult ListAllTvSerial()
{
try
{
TvSerialContext tvContext = new TvSerialContext();
List<TVSerial> tv = tvContext.TvSerials.ToList();
return View(tv);
}
catch (Exception ex)
{
return Content(ex.Message);
}
}
}
Above code works as expected, but if i am doing this :
public ActionResult ListAllSeason(string serial)
{
try
{
TvSerialContext tvContext = new TvSerialContext();
List<TVSerial> tv = tvContext.TvSerials.Where(tvs => tvs.Series_Name == serial).Distinct().ToList();
return View(tv);
}
catch (Exception ex)
{
return Content(ex.Message);
}
}
it return all rows , i just want single row from every series_name and custom field "Series_Name,Season_No,Image_Url_Big"
i don't know how to achieve this.
getting result :
Expected result:-
You could do this by creating a view model and using a .GroupBy() clause
public class TVSerialVM
{
public string SeriesName { get; set; }
public string SeasonNo { get; set; }
public string ImageUrl { get; set; }
}
and the query to project into your view model
List<TVSerialVM> model = tvContext.TvSerials.Where(t => t.Series_Name == serial)
.GroupBy(t => new { t.Series_Name, t.Season_No, t.Image_Url_Big })
.Select(t => new TVSerialVM
{
SeriesName = t.Key.Series_Name,
SeasonNo = t.Key.Season_No,
ImageUrl = t.Key.Image_Url_Big
}).ToList();
Side note: Your duplicating data in the database (the season number and the image url). You should consider moving the image urls to another table with a relationship to the season number.
The reason you are getting multiple values even though you are using distinct is the Distinct method does not know what "equal" is for TVSerial.
You can use Distinct with IEqualityComparer.
https://msdn.microsoft.com/en-us/library/vstudio/bb338049(v=vs.100).aspx
Distinct is not guaranteed to on custom objects it doesn't know what to compare. I have used this SO in the past to make my custom object work with Distinct.
Creating a distinct list of custom type in C#

Entity Framework 6 - child property data not loading

The ManagingAgent child property on the Complex entity is not being loaded with data.... possibly the result of too much mulled wine.
I have logged the SQL on the database calls and the SQL is returning the correct data.
LazyLoading is disabled.
public ApplicationDbContext()
: base("DefaultConnection")
{
this.Configuration.LazyLoadingEnabled = false;
}
Aggregate Root
public class Complex
{
public Complex()
{
Forums = new List<Forum>();
ManagingAgent = new ManagingAgent();
}
[Key]
public int ComplexId { get; set; }
[Required]
public string Name { get; set; }
public string Address1 { get; set; }
public string Address2 { get; set; }
public string Address3 { get; set; }
public int? PostCodeId { get; set; }
public PostCode PostCode { get; set; }
public int? LocationId { get; set; }
public Location Location { get; set; }
public int? CountyId { get; set; }
public County County { get; set; }
public int? ManagingAgentId { get; set; }
public ManagingAgent ManagingAgent { get; set; }
public int? CountOfUnits { get; set; }
public List<Forum> Forums { get; set; }
}
Attempt 1. using Include...
public List<Complex> GetComplexesByUserId(Guid userId)
{
using (var db = new ApplicationDbContext())
{
db.Database.Log = Logger;
var complexIds = db.UserApartments.Where(r => r.UserId == userId)
.Select(c => c.ComplexId).ToList();
return db.Complexes.Where(c => complexIds.Contains(c.ComplexId))
.Include(m => m.ManagingAgent).ToList();
}
}
Attempt 2 - explicitly loading ..same result (SQL returns data correctly but ManagingAgent isn't populated)
public List<Complex> GetComplexesByUserId(Guid userId)
{
using (var db = new ApplicationDbContext())
{
db.Database.Log = Logger;
var complexIds = db.UserApartments.Where(r => r.UserId == userId)
.Select(c => c.ComplexId).ToList();
var list = new List<Complex>();
foreach (var id in complexIds)
{
var complex = db.Complexes.Find(id);
db.Entry(complex).Reference(m => m.ManagingAgent).Load();
list.Add(complex);
}
return list;
}
}
So, to force the load I am doing this.... not good..
foreach (var id in complexIds)
{
var complex = db.Complexes.Find(id);
var managingAgent = db.ManagingAgents.Find(complex.ManagingAgentId);
complex.ManagingAgent = managingAgent;
list.Add(complex);
}
Remove this line...
ManagingAgent = new ManagingAgent();
...from the constructor of the Complex entity. Then it will work. (Generally don't instantiate reference navigation properties in an entity default constructor. EF calls this constructor via reflection when it materializes the entity and "gets confused" if the navigation property already has a reference. I can't explain the "gets confused" better since I don't know the exact mechanism of object materialization with related entities, but the effect is that the loaded child column values are ignored because there is already an instantiated child entity, but just with the useless default values from the ManagingAgent constructor.)