I have this angular JS controller where I am serialising a view model to json which doesnt deserialise on the backend with a web api.
Here is my angular controller constructor..
constructor($scope, $http, $routeParams: IBookingParams) {
this.http = $http;
//get parameters from Recommendation page
this.bookingView = <IBookingViewModel>{};
this.bookingView.CampaignName = $routeParams.CampaignName;
this.bookingView.CampaignSupplierId = $routeParams.CampaignSupplierId;
this.bookingView.SupplierName = $routeParams.SupplierName;
this.bookingView.MediaChannelNames = $routeParams.MediaChannelNames;
this.bookingView.MediaChannelIds = $routeParams.MediaChannelIds;
let livedate = this.GetJSDate($routeParams.LiveDate);
let liveDateTime = this.GetDateTime(livedate);
this.bookingView.LiveDate = liveDateTime;
//populate the rest of our model
this.bookingView.Action = "from angular";
var model = this.bookingView;
let json = JSON.stringify(model);
this.http({
url: "/api/asdabooking",
method: "POST",
data: json
})
.then((response: any) => {
let test = "";
})
.catch((data: any) => {
let test = "";
});
}
Here is my web api
[HttpPost]
[Route("api/asdabooking")]
public async Task<IActionResult> BuildBookingModel([FromBody]BookingViewModel model)
{
try
{
//model is null??!!
return Ok("");
}
catch (Exception ex)
{
base.Logger.LogError(ex.Message, ex);
return BadRequest(ex.Message);
}
}
This is pretty bizarre, the bookingView view model on the front end matches the fields on the backend view model "BookingViewModel. I have inspected the json and all looks ok.
This is my view model
public class BookingViewModel
{
public string CampaignName { get; set; }
public string CampaignSupplierId { get; set; }
public string SupplierName { get; set; }
public List<string> MediaIds { get; set; }
public List<string> MediaChannelNames { get; set; }
public List<MediaChannelViewModel> MediaChannels { get; set; }
public string Action { get; set; }
public DateTime LiveDate { get; set; }
public List<int> MediaChannelIds { get; set; }
public int SupplierId { get; set; }
public bool SuccessfulSave { get; set; }
/// <summary>
/// Track which tab is updating
/// </summary>
public string TabAction { get; set; }
/// <summary>
/// Price summary - list of media channels (tabs)
/// </summary>
public List<MediaSummaryViewModel> MediaSummaries { get; set; }
public string UserMessage { get; set; }
}
This is my json
Often when I run into this issue it is caused from the types within the JSON object not matching the types of your properties that you defined within your model. I would ensure those types match. It also might help folks interested in answering this question to post a snippet of your JSON object as well as your model class.
mediaChannelIds should be
"mediaChannelIds":[
4,
5]
This is because I was getting an array from a query string using $routeParams by referring to the same parameter more than once which is a bad idea.. better to separate values with a character to get an array because you cant make it typesafe with $routeParams.. it will always give you strings.
In the JSON You can miss out fields or pass null no problem and it will still deserialise, but you can't mismatch types or the whole thing comes back as null.
Related
I have a API which is returning a complex JSON and I want to consume that API in Angular-v5 using HTTPClient. I have successfully consumed that API, but the problem is when I want to extract Collections serialized in JSON and map to local arrays in TypeScript then it throws error of undefined for local array and when I try to access the PolicyDetail (which is a Typescript class) properties navigating through like policydetail.policyForms then it throws undefined error, and cannot be used in HTML template that's why.
Although it PolicyDetail.name and other properties works except collections.
Note: API Response is coming and I have tested in Swagger and also seen in Network tap.
Model Coming From API
public class PolicyDetailViewModel
{
public string Name { get; set; }
public string Ref { get; set; }
public ICollection<PolicyDataViewModel> Purpose { get; set; } = new List<PolicyDataViewModel>();
public ICollection<PolicyDataViewModel> Objectives { get; set; } = new List<PolicyDataViewModel>();
public ICollection<DefinitionTermViewModel> Definitions { get; set; } = new List<DefinitionTermViewModel>();
public ICollection<PolicyReferenceViewModel> References { get; set; } = new List<PolicyReferenceViewModel>();
public ICollection<PolicyDataViewModel> Policy { get; set; } = new List<PolicyDataViewModel>();
public ICollection<PolicyDataViewModel> Procedure { get; set; } = new List<PolicyDataViewModel>();
public ICollection<FormViewModel> Forms { get; set; } = new List<FormViewModel>();
public string SupportingInformation { get; set; }
public ICollection<PolicyDataViewModel> Outstanding { get; set; } = new List<PolicyDataViewModel>();
public ICollection<int> SelectedPackages { get; set; } = new List<int>();
public ICollection<int> SelectedRegions { get; set; } = new List<int>();
public bool AnyChanges { get; set; }
public bool IsNewPolicy { get; set; }
}
TypeScript Class
export class PolicyDetail extends AuditableBase
{
name:string;
ref:string;
policyInfo:string;
keyFactsForStaff: string;
policyDataDetails: Array<PolicyDataDetail> = new Array<PolicyDataDetail>();
policyDefinitionTerms: Array<PolicyDefinitionTerm>= new Array<PolicyDefinitionTerm>();
policyreferences: Array<PolicyReference> = new Array<PolicyReference>();
policyForms: Array<PolicyForm> = new Array<PolicyForm>();
selectedKloes: Array<number> = new Array <number>();
selectedRegions: Array<number> = new Array<number>();
selectedClusters: Array<number> = new Array<number>();
selectedLegislations: Array<number> = new Array<number>();
}
Maping Result of HttpRequest To TypeScript
export class PolicyDetailComponent {
public policy: PolicyDetail = new PolicyDetail();
public forms: Array<PolicyForm> = new Array<PolicyForm>();
public policyId: number;
constructor(private policyDetailSvc: PolicyDetailSvc,
private router: Router) { }
getPolicyDetail() {
this.policyDetailSvc.getPolicy(this.policyId).subscribe((result) => {
this.policy = result,//it works
this.forms = result.policyForms; // it doesn't
console.log(result, 'Result - Subscribed'),//it works and shows complete object in JSON
console.log(this.policy, 'This.Policy- Subscribed'),//it works and shows complete object in JSON
console.log(this.forms, 'Forms'),//undefined
console.log(result.policyForms, 'Result Forms'),//undefined
console.log(result.policyreferences, 'Result References')//undefined
});
}
}
Problem is Mapping Forms Arrays and other collection objects
I tried using Local property of forms: PolicyForm[]; but it throws undefined.
I tried accessing Policy.PolicyForms but it also throws undefined
I think I'm taking Typescript as C#, but don't know where I am making mistakes.
If my question is not clear then kindly let me know, I'll clear any other confusion.
Parsing
From what I can see in your question, if you have a raw JSON string in your response, you need to parse it into a JavaScript object...
const policyDetail = JSON.parse(result);
This will work if the result is a string, containing a JSON serialization.
C# Land vs TypeScript Land
Another issue you may find is that your C# class has names such as Name and Ref, so if you are serializing with these names, you'll need to match the casing in the TypeScript...
this.forms = result.PolicyForms
// ^
The problem was accessing the response with wrong collection names, I was getting Forms but I was trying to access using PolicyForms.
So I change the PolicyForms to Forms and end so on and it is working as perfectly it should be.
this.forms = result.policyForms;//it was not working because JSON response was coming forms:[], not PolicyForms:[].
this.forms=result.forms;//
First parameter is a complex type object(JSON) and second parameter is a simple type(String).Here I am using Web API 2.
I am putting my code below.
Web API
public class UserDetailsModel
{
[Key]
[EmailAddress]
public string LoginEmail { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Password { get; set; }
public string DisplayPic { get; set; }
[EmailAddress]
public string AlternateEmail { get; set; }
public string Organization { get; set; }
public string Occupation { get; set; }
public string Contact { get; set; }
public DateTime DoB { get; set; }
public string Gender { get; set; }
public string Country { get; set; }
public string State { get; set; }
public string City { get; set; }
public string Website { get; set; }
public string Info { get; set; }
public DateTime DateOfRegister { get; set; }
//public string LoginIP { get; set; }
public int LoginFlag { get; set; }
}
public int RegisterUser(UserDetailsModel userReg, string LoginIP)
{
.
.
.
}
angularjs
var UserDetails = {
'LoginEmail': $scope.LoginEmail,
'LoginName': $scope.LoginName,
'Password': $scope.Password,
'DoB': $scope.DoB,
'Gender': $scope.Gender,
'City': $scope.City,
'State': $scope.State,
'Country': $scope.Country
};
var request = $http({
method: 'post',
url: urlBase + '/UserDetails',
params: { 'userRegJSON': UserDetails, 'LoginIP': LoginIP }
});
Here in above code, I am getting NULL in UserDetails and 192.152.101.102 in LoginIP in Web API.
var request = $http({
method: 'post',
url: urlBase + '/UserDetails',
data: { 'userRegJSON': UserDetails, 'LoginIP': LoginIP }
});
Here in above code, I am getting NULL in both parameter UserDetails and LoginIP in Web API.
Then how to pass two or more different parameter types in http POST method using angularjs.
You cannot pass 2 types in webAPI.Either you pass everything in a single type or you can do the below
var request = $http({
method: 'post',
url: urlBase + '/UserDetails?LoginIp=' + LoginIP,
data: UserDetails,
});
In the API change the signature to
public int RegisterUser([FromBody]UserDetailsModel userReg, [FromUri]string LoginIP)
{
.
.
.
}
Go throught this:
Use simple and complex types in my api method signatures
POST multiple objects from Angular controller to Web API 2
Webapi doesn't work fairly well when you wish to pass 2 parameters in a POST
method. ModelBinding in Webapi always works against a single object because it maps a model.
There a few workarounds that you can use to make this work:
Use both POST and QueryString Parameters in Conjunction
If you have both complex and simple parameters, you can pass simple parameters on the query string. Your code should actually work with:
something like this
/baseUri/UserDetails?LoginIP=LoginIP
but that's not always possible. In this example it might not be a good idea to pass a user token on the query string though.
Refer to #Ravi A's suggestions for making changes in your code.
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();
}
public partial class User
{
public System.Guid UserId { get; set; }
public Nullable<System.Guid> RoleId { get; set; }
public Nullable<long> MembershipNo { get; set; }
public string Username { get; set; }
public string Password { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Gender { get; set; }
public string Emaiil { get; set; }
public Nullable<decimal> MobileNo { get; set; }
public string Description { get; set; }
public Nullable<System.Guid> ModifiedBy { get; set; }
public Nullable<System.DateTime> ModifiedDate { get; set; }
public virtual Role Role { get; set; }
}
This is my table in DB named Users which is associated with Roles table of DB (as you can see last virtual row at the end above)
Now My problem is simple. I'm using angulars $http.get() method to call my Web Api in MVC 4. When i call it, it gets connected and fetches desired record but it doesn't throw proper result back to .js file or controller.
At .js side I run into error. Every time, it executes .error(jsonResult,config,header,status) .
When I jump on to JsonResult, it shows me below error.
Object
ExceptionMessage: "The 'ObjectContent`1' type failed to serialize the response body for content type 'application/json; charset=utf-8'."
ExceptionType: "System.InvalidOperationException"
InnerException: Object
ExceptionMessage: "Self referencing loop detected for property 'Role' with type
'System.Data.Entity.DynamicProxies.Role_82CA96EA045B1EB47E58B8FFD4472D86502EEA79837B4AE3AD705442F6236E58'.
Path 'Role.Users[0]'."
ExceptionType: "Newtonsoft.Json.JsonSerializationException"
Message: "An error has occurred."
I don't know what's wrong here. Is it json parsing error or something? if so, I've heard and read the articles that webapi in .net handles or throws json itself.
My call happens through
$http.get(apiUrl).success(function (jsonResult, header, config, status) {
debugger;
var number = parseInt(jsonResult.membershipNo) + 1;
$scope.membershipNo = "M" + number;
})
.error(function (jsonResult, header, config, status) {
debugger;
toastr.error('Something went wrong ! Contact Administrator!!!');
});
Edited:
One more thing to mention, .CS side when I fetch single cell value (from DB/table) , it gets returned back to .success() call but when i fetch particular row or all rows, it gets returned to .error() call. I'm using entity frameworkd 6.1.1. and above class is generated by EF-6.1.1.
public partial class Role
{
public Role()
{
this.Permissions = new List<Permission>();
this.Users = new List<User>();
}
public System.Guid RoleId { get; set; }
public string RoleName { get; set; }
public string Description { get; set; }
public Nullable<System.Guid> ModifiedBy { get; set; }
public Nullable<System.DateTime> ModifiedDate { get; set; }
public virtual ICollection<Permission> Permissions { get; set; }
public virtual ICollection<User> Users { get; set; }
}
Hi you can solve that in 2 easy steps
First Step: Create globalConfig class where you can set ignoring ReferenceLoopHandling (http://james.newtonking.com/json/help/index.html?topic=html/SerializationSettings.htm) and if you crating js app you can set as well to remove xml formaters and always get return from Webapi as JSON string is usefull for debugging. So in your app_start folder add class GlobalConfig like below:
public class GlobalConfig
{
public static void CustomizeConfig(HttpConfiguration config)
{
// Remove Xml formatters. This means when we visit an endpoint from a browser,
// Instead of returning Xml, it will return Json.
//that is optional
config.Formatters.Remove(config.Formatters.XmlFormatter);
GlobalConfiguration.Configuration.Formatters.JsonFormatter.SerializerSettings.ReferenceLoopHandling =
Newtonsoft.Json.ReferenceLoopHandling.Ignore;
}
}
Second Step: In Global.asax set your custom configuration to do that please add code below to method Application_Start():
GlobalConfig.CustomizeConfig(GlobalConfiguration.Configuration);
it sounds like:
the problem is that EF is using lazy loading that is not materialized in time of constructing this, on role. EF from early version has switched lazy loading on by default.
Suggested solution
Create subset of you user class, with the parts that you really need.
=> Its bad practise to fetch too much data that you are not gonna need.
I'm just learning ASP.NET MVC 3, And recently I tried a lot of times to pass arrays/lists/ICollections etc. but couldn't. everytime the list was empty.
For example, the current project:
Model:
public class Video
{
public int VideoID { get; set; }
public string Name { get; set; }
public ICollection<string> Tags { get; set; }
}
Initializer - Seed:
protected override void Seed(DatabaseContext context)
{
var videos = new List<Video>
{
new Video {
Name = "Video01",
Tags = new List<string> { "tag1", "tag2" },
};
videos.ForEach(s => context.Videos.Add(s));
context.SaveChanges();
base.Seed(context);
}
In the view: I do get the Name property, but the Tags are completely empty.
In the debug I get Tags - Count: 0.
This is not the first time it happens to me, to be honest it happens every single time when I try to pass those kind of stuff. a bit of info about the project:
ASP.NET MVC 3, Entity-Framework:Code First, SqlServerCe.4.0.
Crean an entity Tag
public class Video
{
public int VideoID { get; set; }
public string Name { get; set; }
public ICollection<Tag> Tags { get; set; }
}
public class Tag
{
public int TagId { get; set; }
public int VideoId { get; set; }
public string TagText { get; set; }
}
or store tags to one field separated with comma /semicolon or whatever fits for your solution
By default Entity Framework doesn't load associations of an entity, you need to specify it explicitly:
var videos = context.Videos.Include("Tags");