Asp.net Core does not serialize my new type - asp.net-core

Am using .net core +angular 5, and trying to return a list, but one field is null in JSON response. Am using Postman to trigger debugging and saw in VS that the field has a value coming from the DB.
Don't know why it doesn't in the JSON response.
[HttpGet("[action]")]
public IEnumerable<HikingTrail> HikingTrails()
{
var dbOptions = new DbContextOptionsBuilder<HikingTrailContext>();
dbOptions.UseSqlServer("Server = (localdb)\\mssqllocaldb; Database = HikingApp");
var dbContext = new DAO.HikingTrailContext(dbOptions.Options);
return dbContext.HikingTrails.ToList();
}
This returns:
I'm interested in the "mountainRange" field not being null. In debug window it has the right value.
{
"url": null,
"hikingTrailId": 159,
"mountainRange": null,
"name": "My custom name",
"startPoint": null,
"endPoint": null,
"trailCheckpoints": null,
"type": 2,
"dificulty": null,
"duration": "2 1/2 - 3 h",
"minDuration": "00:00:00",
"maxDuration": "00:00:00",
"seasonality": "mediu",
"equipmentLevel": null,
"trailMarking": null,
"hasTrailType": false
},
I was thinking maybe it's EF Core, and have made this 2nd try (i.e. added Include() to dbContext query):
[HttpGet("[action]")]
public IEnumerable<HikingTrail> HikingTrails()
{
var dbOptions = new DbContextOptionsBuilder<HikingTrailContext>();
dbOptions.UseSqlServer("Server = (localdb)\\mssqllocaldb; Database = HikingApp");
var dbContext = new DAO.HikingTrailContext(dbOptions.Options);
return dbContext.HikingTrails.Include( x => x.MountainRange).ToList();
}
Could not get any response in Postman.
EDIT:
public class HikingTrailContext : DbContext
{
public HikingTrailContext(DbContextOptions<HikingTrailContext> options) : base(options)
{
}
public HikingTrailContext():base(){
}
public DbSet<HikingTrail> HikingTrails { get; set; }
public DbSet<MountainRange> MountainRanges { get; set; }
public DbSet<TrailScrapingSessionInfo> TrailScrapingHistory { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
}
}
public class HikingTrail
{
[Key]
public int HikingTrailId { get; set; }
public HikingTrail() { }
public MountainRange MountainRange { get; set; }
public String Name { get; set; }
public Location StartPoint { get; set; }
public Location EndPoint { get; set; }
public List<Location> TrailCheckpoints { get; }
public TrailType Type => TrailType.Undetermined;
public String Dificulty { get; set; }
public String Duration { get; set; }
public TimeSpan MinDuration { get; set; }
public TimeSpan MaxDuration { get; set; }
public String Seasonality { get; set; }
public String EquipmentLevel { get; set; }
public String TrailMarking { get; set; }
public String URL;
public bool HasTrailType
{
get
{
return this.Type != TrailType.Undetermined;
}
}
public override bool Equals(object obj)
{
return (((HikingTrail)obj).Name == this.Name);
}
public override int GetHashCode()
{
int hash = 17;
// Suitable nullity checks etc, of course :)
hash = hash * 23 + Name.GetHashCode();
hash = hash * 23 + Type.GetHashCode();
hash = hash * 23 + StartPoint.GetHashCode();
return hash;
}
public override string ToString()
{
return Name.ToString();
}
}
EDIT :
I profiled the db on dbContext.HikingTrails.Include(x => x.MountainRange).Where(x => x.MountainRange != null).ToList(); and the generated query is OK, meaning it has a Name column for MountainRange as well.
P.S.: several fields are null, but those ones don't have data yet.

found one solution, Projection to an anonymous type. Also had to be careful not to have two fields with the same name of "Name"
[HttpGet("[action]")]
public dynamic HikingTrails3()
{
var dbOptions = new DbContextOptionsBuilder<HikingTrailContext>();
dbOptions.UseSqlServer("Server = (localdb)\\mssqllocaldb; Database = HikingApp");
var dbContext = new DAO.HikingTrailContext(dbOptions.Options);
var trails = dbContext.HikingTrails.Include(x => x.MountainRange).
Select( i =>new { Name= i.Name, MountainRangeName = i.MountainRange.Name, i.Duration,
i.Dificulty,i.EquipmentLevel, i.Seasonality, i.Type }).ToList();
return trails;
}

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:

AutoMapper Custom Value Resolvers async

I want to convert BotViewModel to Bot using AutoMapper.
There is an example input below from type BotViewModel. If I want to add a new bot to the database, that model should be matched/mapped to Bot model and Bot's foreign keys should be resolved.
In other words, BotViewModel.Symbol should be matched to CryptoPair.Symbol and BotViewModel.Interval to TimeInterval.Interval.
{
"id": 0,
"name": "Bot 1",
"status": true,
"symbol": "LTCBTC",
"interval": 6
}
My code below is working exactly as I described it above. It uses http://docs.automapper.org/en/stable/Custom-value-resolvers.html. The thing is that I want to make _context.CryptoPairs.FirstOrDefault async FirstOrDefault => FirstOrDefaultAsync.
I want to do that because I feel like that's not the best practice. Basically, I'm looking for an advice if that's okay or not. By the way, if the symbol or interval cannot be matched because it simply doesn't exist in the database, var bot = _mapper.Map<Bot>(botViewModel); should just throw an exception which is later handled by my controller.
public class CryptoPairResolver : IValueResolver<BotViewModel, Bot, int>
{
private readonly BinanceContext _context;
public CryptoPairResolver(BinanceContext context)
{
_context = context;
}
public int Resolve(BotViewModel source, Bot destination, int destMember, ResolutionContext context)
{
return _context.CryptoPairs.FirstOrDefault(p => p.Symbol == source.Symbol).Id;
}
}
public class TimeIntervalResolver : IValueResolver<BotViewModel, Bot, int>
{
private readonly BinanceContext _context;
public TimeIntervalResolver(BinanceContext context)
{
_context = context;
}
public int Resolve(BotViewModel source, Bot destination, int destMember, ResolutionContext context)
{
return _context.TimeIntervals.FirstOrDefault(i => i.Interval == source.Interval).Id;
}
}
public class MappingProfile : Profile
{
public MappingProfile()
{
CreateMap<BotViewModel, Bot>()
.ForMember(dest => dest.CryptoPairId, opt => opt.MapFrom<CryptoPairResolver>())
.ForMember(dest => dest.TimeIntervalId, opt => opt.MapFrom<TimeIntervalResolver>());
}
}
Models:
public class Bot
{
public int Id { get; set; }
public string Name { get; set; }
public bool Status { get; set; }
public int CryptoPairId { get; set; }
public CryptoPair CryptoPair { get; set; }
public int TimeIntervalId { get; set; }
public TimeInterval TimeInterval { get; set; }
}
public class BotViewModel
{
public int Id { get; set; }
public string Name { get; set; }
public bool Status { get; set; }
public string Symbol { get; set; }
public KlineInterval Interval { get; set; }
}
public class CryptoPair
{
public int Id { get; set; }
public string Symbol { get; set; }
public List<Bot> Bots { get; set; }
}
public class TimeInterval
{
public int Id { get; set; }
public KlineInterval Interval { get; set; }
public List<Bot> Bots { get; set; }
}
Edit:
public async Task<Bot> CreateAsync(BotViewModel botViewModel)
{
// Map BotViewModel to Bot
var cryptoPair = await _context.CryptoPairs.FirstOrDefaultAsync(e => e.Symbol == botViewModel.Symbol);
if (cryptoPair == null)
{
throw new BadRequestException();
}
var timeInterval = await _context.TimeIntervals.FirstOrDefaultAsync(e => e.Interval == botViewModel.Interval);
if (timeInterval == null)
{
throw new BadRequestException();
}
var bot = new Bot
{
Name = botViewModel.Name,
Status = botViewModel.Status,
CryptoPairId = cryptoPair.Id,
TimeIntervalId = timeInterval.Id
};
// Create code
_context.Bots.Add(bot);
await _context.SaveChangesAsync();
return bot;
}

Asp.net core 2 webapi post related data insert

How can I tell EF Core to not insert related data on post ?
this are my models for Deposito and Sucursal
public class IDeposito
{
[Key]
public int Id { get; set; }
public string Descrip { get; set; }
public string Alias { get; set; }
public Boolean Activo { get; set; }
public ISucursal Sucursal { get; set; }
}
public class ISucursal
{
[Key]
public int Id { get; set; }
public string Descrip { get; set; }
public string Alias { get; set; }
public Boolean Activo { get; set; }
}
This is my controller
[HttpPost]
public IActionResult Create([FromBody] IDeposito postData)
{
if (postData == null)
{
return BadRequest();
}
_context.Depositos.Add(postData);
_context.SaveChanges();
return CreatedAtRoute("GetDeposito", new { ID = postData.Id }, postData);
}
When I post this model
{
"Descrip": "Neuquen",
"Alias": "NQN",
"Activo": true,
"Sucursal": {
"Id": 1,
"Descrip": "Comodoro Rivadavia",
"Alias": "CR",
"Activo": true
}
}
It fails because it tries to insert into table "Sucursal", although there already exists a "Sucursal" with id: 1.
Is there anyway I can tell EF Core to not update the related tables ? thank you very much
First approach if you want connect new IDeposito to existing ISucursal with Id = 1 then use:
postData.Sucursal = _context.Sucursals.Find(postData.Sucursal.Id);
_context.Depositos.Add(postData);
_context.SaveChanges();
return CreatedAtRoute("GetDeposito", new { ID = postData.Id }, postData);
If you want add only IDeposito without any sucursal:
postData.Sucursal = null;
_context.Depositos.Add(postData);
_context.SaveChanges();
return CreatedAtRoute("GetDeposito", new { ID = postData.Id }, postData);

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))

New records inserted in foreign key table when inserting in parent table

I am new to Asp.net MVC and working on a simple blog application (Asp.Net MVC5, EF6) for learning.
I am using repository pattern for the solution architecture with EF Code first migration, Ninject for DI. On the client side, I am using jQuery Grid for Admin to manage Posts, Categories and Tags.
- Blog.Model: Post.cs, Category.cs, Tags.cs
public class Post
{
[Required(ErrorMessage = "Id is required")]
public int Id { get; set; }
[Required(ErrorMessage = "Title is required")]
[StringLength(500, ErrorMessage = "Title cannot be more than 500 characters long")]
public string Title { get; set; }
[Required(ErrorMessage = "Short description is required")]
public string ShortDescription { get; set; }
[Required(ErrorMessage = "Description is required")]
public string Description { get; set; }
public bool Published { get; set; }
[Required(ErrorMessage = "PostedOn date is required")]
public DateTime PostedOn { get; set; }
public DateTime? ModifiedOn { get; set; }
[ForeignKey("Category")]
public virtual int CategoryId { get; set; }
public virtual Category Category { get; set; }
public virtual IList<Tag> Tags { get; set; }
}
public class Category
{
[Key]
public int CategoryId { get; set; }
[Required(ErrorMessage = "Category Name is required")]
[StringLength(500,ErrorMessage = "Category name length cannot exceed 500")]
public string Name { get; set; }
[Required(ErrorMessage = "Category Name is required")]
[StringLength(500, ErrorMessage = "Category name length cannot exceed 500")]
public string Description { get; set; }
[JsonIgnore]
public virtual IList<Post> Posts { get; set; }
}
public class Tag
{
public int Id { get; set; }
[Required(ErrorMessage = "Name is required")]
[StringLength(500, ErrorMessage = "Name length should not exceed 500 characters")]
public string Name { get; set; }
public string Description { get; set; }
[JsonIgnore]
public IList<Post> Posts { get; set; }
}
- Blog.Repository: BlogRepository, IBlogRepository, BlogContext
public interface IBlogRepository
{
int SavePost(Post post);
//Other methods...
}
public class BlogRepository : BlogContext, IBlogRepository
{
public BlogContext _db;
public BlogRepository(BlogContext db)
{
_db = db;
}
public int SavePost(Post post)
{
_db.Posts.Add(post);
_db.SaveChanges();
return post.Id;
}
//Other implementations...
}
public class BlogContext : DbContext, IDisposedTracker
{
public BlogContext() : base("BlogDbConnection") { }
public DbSet<Post> Posts { get; set; }
public DbSet<Tag> Tags { get; set; }
public DbSet<Category> Categories { get; set; }
public bool IsDisposed { get; set; }
protected override void Dispose(bool disposing)
{
IsDisposed = true;
base.Dispose(disposing);
}
- Blog.Web: AdminController.cs, NinjectWebCommon.cs
AdminController sends/consumes data in Json format.
public class AdminController : Controller
{
private readonly IBlogRepository _blogRepository;
public AdminController(IBlogRepository blogRepository)
{
_blogRepository = blogRepository;
}
//POST: /Admin/CreatePost
[HttpPost, ValidateInput(false)]
public ContentResult CreatePost([ModelBinder(typeof(PostModelBinder))] Post model)
{
string json;
ModelState.Clear();
if (TryValidateModel(model))
{
var id = _blogRepository.SavePost(model);
json = JsonConvert.SerializeObject(
new
{
id = id,
success = true,
message = "Post saved successfully."
});
}
else
{
json = JsonConvert.SerializeObject(
new
{
id = 0,
success = false,
message = "Post not saved."
});
}
return Content(json, "application/json");
}
}
public static class NinjectWebCommon
{
private static void RegisterServices(IKernel kernel)
{
kernel.Bind<BlogContext>().ToSelf(); //This isn't helping either
kernel.Bind<IBlogRepository>().To<BlogRepository>();
}
}
I am using Custom Model Binding because I was getting validation exception while saving post since list of Categories and Tags received from grid do not map to actual objects in the application model. Therefore in the custom model binding, I am populating Post object with actual objects received from grid. This Post object is Sent to controller which Save to database using DbContext and Repository.
public class PostModelBinder : DefaultModelBinder
{
public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
var post = (Post)base.BindModel(controllerContext, bindingContext);
**var blogRepository = new BlogRepository(new BlogContext());**//I think here I need to inject the dependency for BlogContext, but don't know how to do that.
if (post.Category != null)
{
post.Category = blogRepository._db.Categories.AsNoTracking().Single(c => c.CategoryId == post.Category.CategoryId);
}
var tags = bindingContext.ValueProvider.GetValue("Tags").AttemptedValue.Split(',');
if (tags.Length > 0)
{
post.Tags = new List<Tag>();
foreach (var tag in tags)
{
var id = int.Parse(tag.Trim());
post.Tags.Add(blogRepository._db.Tags.AsNoTracking().Single(t => t.Id == id));
}
}
if (bindingContext.ValueProvider.GetValue("oper").AttemptedValue.Equals("edit"))
post.ModifiedOn = DateTime.UtcNow;
else
post.PostedOn = DateTime.UtcNow;
return post;
}
}
Issue: When the Post is saved, data context inserts new rows for Category and Tags in their respective tables. The newly created post refers to new Category (Id:22) under Foreign key column.
Post:
Category:
Tag:
I think the reason for this is that when entity is saved it is attached to a different ObjectContext and I need to attach it to current context but do not know how? I found similar question asked before but there isn't an accepted answer to that. Any help would be greatly appreciated.
I was able to resolve above issue by attaching category and tags value to objectcontext manually, which indicates EF the changes it needs to make. This way it doesn't create new entries in Category and Tag's parent tables.
public int SavePost(Post post)
{
//attach tags to db context for Tags to tell EF
//that these tags already exist in database
foreach (var t in post.Tags)
{
_db.Tags.Attach(t);
}
//tell EF that Category already exists in Category table
_db.Entry(post.Category).State = EntityState.Modified;
_db.Posts.Add(post);
_db.SaveChanges();
return post.Id;
}
public void EditPost(Post post)
{
if (post == null) return;
//get current post from database
var dbPost = _db.Posts.Include(p => p.Tags).SingleOrDefault(p => p.Id == post.Id);
//get new list of tags
var newTags = post.Tags.Select(tag => new Tag() { Id = tag.Id, Name = tag.Name, Description = tag.Description }).ToList();
if (dbPost != null)
{
//get category from its parent table and assign to db post
dbPost.Category = _db.Categories.Find(post.Category.CategoryId); ;
//set scalar properties
_db.Entry(dbPost).CurrentValues.SetValues(post);
//remove tags from post in database
foreach (var t in dbPost.Tags.ToList())
{
if (!newTags.Contains(t))
{
dbPost.Tags.Remove(t);
}
}
//add tags to post in database
foreach (var t in newTags)
{
if (dbPost.Tags.All(p => p.Id != t.Id))
{
var tagInDb = _db.Tags.Find(t.Id);
if (tagInDb != null)
{
dbPost.Tags.Add(tagInDb);
}
}
}
}
//save changes
_db.SaveChanges();
}