In my WebApi controller I have a few methods that return objects retrieved from a database which are serialized to Json. Everything works fine if a method serializes and returns only a single object, it fails when it tries to serialize a collection of objects.
This is my model class:
[Table("Athlete")]
public partial class Athlete
{
public Athlete()
{
Event = new HashSet<Event>();
User = new HashSet<User>();
}
[Required]
[StringLength(32)]
[DisplayName("First name")]
public string FirstName { get; set; }
[Required]
[StringLength(32)]
[DisplayName("Last name")]
public string LastName { get; set; }
[StringLength(32)]
[DisplayName("Sport")]
public string Sport { get; set; }
[Key]
[Column(TypeName = "numeric")]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public decimal Athlete_ID { get; set; }
[DataMember]
[Column(TypeName = "numeric")]
public decimal? Team_Team_ID { get; set; }
[NotMapped]
[DisplayName("Team")]
public string TeamName { get; set; }
[JsonIgnore]
public virtual Team Team { get; set; }
[JsonIgnore]
public virtual ICollection<Event> Event { get; set; }
[JsonIgnore]
public virtual ICollection<User> User { get; set; }
}
This works fine:
[HttpGet]
public IHttpActionResult GetById(int id)
{
var athlete = _db.Athlete
.Where(a => a.Athlete_ID == id)
.FirstOrDefault();
if (athlete != null)
{
return Json<Athlete>(athlete);
}
return NotFound();
}
The following method causes a serialization error (System.InvalidOperationException)
(The 'ObjectContent`1' type failed to serialize the response body for content type 'application/json; charset=utf-8'.)
The inner exception's message is "Self referencing loop detected for property 'ApplicationInstance' with type 'ASP.global_asax'. Path 'Request.Properties.MS_HttpContext.ApplicationInstance.Context'."
[HttpGet]
public IHttpActionResult GetAllAthletes()
{
var athletes = _db.Athlete.ToArray();
if (athletes != null)
{
return Ok(Json<IEnumerable<Athlete>>(athletes));
}
return NotFound();
}
I've already tried to change the serialization settings in WebApiConfig.cs like in this question but nothing has worked so far.
Any help would be appreciated.
I've managed to find a way to work-around this in a semi-elegant manner. I'm not completely happy with this but a man's gotta do what a man's gotta do.
In case anyone needs this in the future:
Create a class that implements the IHttpActionResult interface:
public class MyJsonResult : IHttpActionResult
{
object _value;
HttpRequestMessage _request;
public MyJsonResult(object value, HttpRequestMessage request)
{
_value = value;
_request = request;
}
public Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
{
var response = new HttpResponseMessage()
{
Content = new StringContent(JsonConvert.SerializeObject(_value), Encoding.UTF8, "application/json"),
RequestMessage = _request,
StatusCode = HttpStatusCode.OK
};
return Task.FromResult(response);
}
}
Then use it in a following way:
[HttpGet]
public IHttpActionResult GetAllAthletes()
{
var athletes = _db.Athlete;
if (athletes != null)
{
return new MyJsonResult(athletes, Request);
}
return NotFound();
}
Related
I have two classes in my database which are defined as classes, fed into entity and then called from the API
The full method is below for the calls. The first call works fine, the second throws the exception
public async Task<ActionResult<List<QuizForms>>> GetQuiz([FromQuery]string id)
{
var form = await _context.QuizForms.Where(t=>t.QuizId == id).ToListAsync();
if (form == null)
{
return NotFound();
}
var elem = new List<Element>();
foreach(var e in form)
{
var data = await _context.Element.Where(t => t.ElementId == e.ElementId).ToListAsync();
elem.AddRange(data);
e.Element.AddRange(elem);
}
return form;
}
When the var data line is hit, an excception is thrown
Microsoft.Data.SqlClient.SqlException (0x80131904): Invalid column name 'QuizFormsFormId'.
It looks like the name of the class and column name are being concatenated and the used as the query parameter.
The two classes look like this
public class QuizForms
{
[Key]
public int FormId { get; set; }
public string QuizId { get; set; } = "";
#nullable enable
public string? Title { get; set; }
public int? ElementId { get; set; }
public List<Element>? Element { get; set; }
public int? PreviousId { get; set; }
public int? NextId { get; set; }
#nullable disable
}
and
public class Element
{
[Key]
public int Id { get; set; }
public int ElementId { get; set; }
#nullable enable
public int? MathsId { get; set; }
public int? QuestionId { get; set; }
public int? InformationId { get; set; }
public int? AnswerId { get; set; }
#nullable disable
public string QuizId { get; set; } = "";
}
Is it because I'm not using Id for the primary key or do I need to do something else so the class and property aren't concatented like this?
I created a SqlMapper.TypeHandler to map a Customer object into a CreditAccount class as follows:
public class CustomerTypeHandler : SqlMapper.TypeHandler<Customer>
{
public override Customer Parse(object value)
{
throw new NotImplementedException();
}
public override void SetValue(IDbDataParameter parameter, Customer
value)
{
throw new NotImplementedException();
}
}
public class CreditAccount
{
public int AccountId { get; set; }
public Customer Customer{ get; set; }
}
public class Customer
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string MiddleName { get; set; }
}
When I connect to the DB and call a sproc, the CustomerTypeHandler Parse method is never called and my CreditAccount object is populated with the AccountId only. The Customer object is null.
I am calling it as follows:
public async Task<CreditAccount> GetCreditAccount(int accountId)
{
var sql = "MY PROC NAME HERE";
var parameters = new DynamicParameters();
parameters.Add("#AccountId", accountId);
SqlMapper.AddTypeHandler(new CustomerTypeHandler());
using (IDbConnection connection = Connection)
{
connection.Open();
var account = await connection.QueryFirstAsync<CreditAccount>(sql, parameters, commandType: CommandType.StoredProcedure);
return account;
}
}
}
I placed a breakpoint in the Parse method and it is never called.
The database connection works, and I am getting the AccountId.
My environment;
.NET Core 2.2
Dapper 1.50.5
The code is simple enough. I get not exceptions. Any ideas?
A year has passed and now there is no this error in Dapper 2.0.30.
I checked it on jsonb columns in Postgres.
using Dapper;
using Newtonsoft.Json;
using Npgsql;
using System;
using System.Data;
public class CreditAccount
{
public int AccountId { get; set; }
public Customer Customer { get; set; }
}
public class Customer
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string MiddleName { get; set; }
}
public class CustomerJsonObjectTypeHandler : SqlMapper.TypeHandler<Customer>
{
public override void SetValue(IDbDataParameter parameter, Customer value)
{
parameter.Value = (value == null)
? (object)DBNull.Value
: JsonConvert.SerializeObject(value);
parameter.DbType = DbType.String;
}
public override Customer Parse(object value)
{
return JsonConvert.DeserializeObject<Customer>(value.ToString());
}
}
Example using this classes - all work fine.
static void Main(string[] args)
{
using (var connection = GetDefaultConnection())
{
connection.Open();
var customer = new Customer
{
FirstName = "Gaday",
LastName = "Ivanova",
MiddleName = "Petrovich"
};
var jsonData = JsonConvert.SerializeObject(customer);
var strQuery = $"SELECT 10500 as AccountId,'{jsonData}'::jsonb as Customer";
SqlMapper.AddTypeHandler(new CustomerJsonObjectTypeHandler());
try
{
var data = connection.QueryFirst<CreditAccount>(strQuery);
}
catch(Exception ex)
{
Console.WriteLine(ex.Message);
}
}
}
I got the error Invalid object name 'dbo.Staffs'. but I'm not sure why. I actually deleted and recreated my database with EF because previously I had other errors. But I'm quite sure I recreated it correctly because I've done it in the same way for other programs and it works fine.
.edmx database diagram
Controller
private StaffPortalDBEntities1 db = new StaffPortalDBEntities1();
SqlConnection cnn = new SqlConnection(ConfigurationManager.ConnectionStrings["StaffPortalDBConnectionString"].ConnectionString);
[Authorize]
public ActionResult Index()
{
var userEmail = User.Identity.Name;
var model = db.Staffs.Where(i => i.Email == userEmail).Include("Histories").Include("CurrentApplications").FirstOrDefault();
return View(model);
}
I got the error is for the line var model = db.Staffs.Where(i => i.Email == userEmail).Include("Histories").Include("CurrentApplications").FirstOrDefault();
Generated Staff class
public partial class Staff
{
public Staff()
{
this.Histories = new HashSet<History>();
this.CurrentApplications = new HashSet<CurrentApplication>();
}
public int StaffID { get; set; }
public string Name { get; set; }
public string Email { get; set; }
public Nullable<decimal> AllocatedLeave { get; set; }
public Nullable<int> BalanceLeave { get; set; }
public virtual ICollection<History> Histories { get; set; }
public virtual ICollection<CurrentApplication> CurrentApplications { get; set; }
}
Try this:
var model = db.Staffs.Where(i => i.Email == userEmail).Include(x=>x.Histories).Include(x=>x.CurrentApplications).FirstOrDefault();
I have several ServiceStack ORMLite POCO, one is Company below.
public class Company
{
[AutoIncrement]
public int id { get; set; }
public string company { get; set; }
public int? companyNo { get; set; }
public bool? active { get; set; }
}
If two properties are valid in the following request: req.company="ABC Company", req.active=ture, and all other properties are null. Then it can return all records matching the two properties. The code may look like below:
public object Get(Company req)
{
return Db.Select<Company>().Where<Company>(req);
}
Does ServiceStack ORMLite have such a WHRER to auto-match the valid properties in the request DTO?
This is not a feature in OrmLite, but it's available in AutoQuery where you just need to define the Request DTO you want to query, e.g:
[Route("/company/search")]
public class QueryCompany : IQuery<Company>
{
public int Id { get; set; }
public string Company { get; set; }
public int? CompanyNo { get; set; }
public bool? Active { get; set; }
}
With just the Request DTO, ServiceStack automatically creates the Service for you which you can query like any other Service.
Enable AutoQuery
You can enable AutoQuery by registering the AutoQuery Feature, e.g:
Plugins.Add(new AutoQueryFeature { MaxLimit = 100 });
AutoQuery is available in the ServiceStack.Server NuGet package:
PM> Install-Package ServiceStack.Server
Thanks mythz. It works for me. My code is like below:
// ====== Model.cs ========
[Route("/company/search")]
public class QueryableCompany : QueryBase<Company>
{
public int? Id { get; set; }
public string Company { get; set; }
public int? CompanyNo { get; set; }
public bool? Active { get; set; }
}
public class Company
{
[AutoIncrement]
public int id { get; set; }
public string company { get; set; }
public int companyNo { get; set; }
public bool active { get; set; }
}
// ====== Service.cs ========
public IAutoQuery AutoQuery { get; set; }
public object Get(QueryableCompanies dto)
{
var q = AutoQuery.CreateQuery(dto, Request.GetRequestParams());
var r = AutoQuery.Execute(dto, q);
return r.Results;
}
// ====== Global.asax.cs ========
public override void Configure(Container container)
{
//...
Plugins.Add(new AutoQueryFeature { MaxLimit = 100 });
//...
}
Then, I have two more questions based on the code above.
1) Since I have a lot of request DTOs, their code in Get(QueryableXXX dto) is all the same; How can I use a single generic Get() method to return all different types of DTO, like:
public object Get<T>(T dto) where T : IQuery
{
var q = AutoQuery.CreateQuery(dto, Request.GetRequestParams());
return AutoQuery.Execute(dto, q).Results;
}
2) In the Company example above, class QueryableCompany seems so similar to class Company, can AutoQuery provide some Attributes to class Company's members, and avoid to create another similar QueryableCompany?
I am using Simple Membership and a UserProfile table that maintains UserId and UserName:
public partial class UserProfile
{
public UserProfile()
{
this.webpages_Roles = new List<webpages_Roles>();
}
public int UserId { get; set; }
public string UserName { get; set; }
public virtual ICollection<webpages_Roles> webpages_Roles { get; set; }
}
With Entity Framework I am running the following which is inside my Context:
public partial class UowContext : DbContext
// code to set up DbSets here ...
public DbSet<Content> Contents { get; set; }
private void ApplyRules()
{
var r1 = new Random();
var r2 = new Random();
foreach (var entry in this.ChangeTracker.Entries()
.Where(
e => e.Entity is IAuditableTable &&
(e.State == EntityState.Added) ||
(e.State == EntityState.Modified)))
{
IAuditableTable e = (IAuditableTable)entry.Entity;
if (entry.State == EntityState.Added)
{
e.CreatedBy = // I want to put the integer value of UserId here
e.CreatedDate = DateTime.Now;
}
e.ModifiedBy = // I want to put the integer value of UserId here
e.ModifiedDate = DateTime.Now;
}
}
Here is the schema showing how user information is stored. Note that I store the integer UserId and not the UserName in the tables:
public abstract class AuditableTable : IAuditableTable
{
public virtual byte[] Version { get; set; }
public int CreatedBy { get; set; }
public DateTime CreatedDate { get; set; }
public int ModifiedBy { get; set; }
public DateTime ModifiedDate { get; set; }
}
Here's an example of a controller action that I use:
public HttpResponseMessage PostContent(Content content)
{
try
{
_uow.Contents.Add(content);
_uow.Commit();
var response = Request.CreateResponse<Content>(HttpStatusCode.Created, content);
return response;
}
catch (DbUpdateException ex)
{
return Request.CreateErrorResponse(HttpStatusCode.Conflict, ex);
}
}
I then have:
public class UowBase : IUow, IDisposable
{
public UowBase(IRepositoryProvider repositoryProvider)
{
CreateDbContext();
repositoryProvider.DbContext = DbContext;
RepositoryProvider = repositoryProvider;
}
public IRepository<Content> Contents { get { return GetStandardRepo<Content>(); } }
and:
public class GenericRepository<T> : IRepository<T> where T : class
{
public GenericRepository(DbContext dbContext)
{
if (dbContext == null)
throw new ArgumentNullException("An instance of DbContext is required to use this repository", "context");
DbContext = dbContext;
DbSet = DbContext.Set<T>();
}
public virtual void Add(T entity)
{
DbEntityEntry dbEntityEntry = DbContext.Entry(entity);
if (dbEntityEntry.State != EntityState.Detached)
{
dbEntityEntry.State = EntityState.Added;
}
else
{
DbSet.Add(entity);
}
}
How can I determine the UserId from inside of my Context so I can populate the Id in my tables?
In Code you will have UserName with you through:
HttpContext.Current.User.Identity.Name
you can than query UserProfile table against that Name and get the UserId from there and than assign it to ModifiedBy attribute.
Make sure that you query UserProfile table outside the foreach loop :)