MVC model binding complex type that has a property of complex type collection with Angular JS - angularjs

I am developing an Web application using Angular JS and ASP.NET MVC. But I am having a problem with model binding complex type. I am uploading list of files, list of string basically. In the data base, they(list of string and list of file) are the same columns and table because I save the path after file is uploaded. For string I save the same column with file path. For example, a database table has Id(int) and Data(string) columns.
Then I save the string data posted by AngularJS in the Data column, for file path, in the data column as well. But the point is, I need to remember the order. For example, user add a text field and then enter value, then add file field dynamically and then choose file, then add a text field and enter value again. So the order must be [ "text 1 value" , File , "text 2 value" ]. But the problem is we cannot bind the list of data mixed both HttpPostedFileBase for file and string for text value. So what I did was created view models like below.
public class CreateBlogVM
{
[Required]
[MaxLength(250)]
public string Title { get; set; }
[Required]
public ContentModel[] TextContents { get; set; }
[Required]
public ContentFileModel[] Files { get; set; }
}
public class ContentModel
{
public int OrderNo { get; set; }
public string Content { get; set; }
}
public class ContentFileModel
{
public int OrderNo { get; set; }
public HttpPostedFileBase File { get; set; }
}
As you can see in the above, CreateBlogVM will be the ViewModel that I am binding now. That class will have two properties of complex type- TextContents and Files that I explained above what I was doing. So to remember the order, I created a complex type with OrderNo field (Client will pass this value) as you can see above since we cannot bind list of data something like this
[HttpPostedFileBase, String, String, HttpPostedFileBase]
But the problem is when I post data from Angular js, all values are null and not binding the data. Only the "Title" value is binding.
var textContents = new Array();
var photoContents = new Array();
for(var i=0; i<$scope.rows.length; i++)
{
if($scope.rows[i].type=="text")
{
var value = $scope.rows[i].value;
if (value == "" || value == null) {
showAlert("Text field should not be empty", "danger");
return;
}
else {
var content = { OrderNo: i, Content: value }
textContents.push(content)
}
}
else if($scope.rows[i].type=="photo")
{
var file = $scope.rows[i].file;
if(file==null)
{
showAlert("Photo file is required", "danger");
return;
}
else {
var content = { OrderNo: i, File: file };
photoContents.push(file);
}
}
}
var fd = new FormData();
fd.append('Title', $scope.title);
fd.append("TextContents", textContents);
fd.append("Files", photoContents);
$http.post("/Admin/Blog/Create", fd, {
transformRequest: angular.identity,
headers: { 'Content-Type': undefined }
})
.success(function () {
})
.error(function () {
});
Above code is how I submit data to server. When I post data, all values are null and mvc is not binding data. But when I bind values without using complex type like this
public JsonResult Create(HttpPostedFileBase files, String contents, String title)
But if I bind like above, I cannot order the files and string contents. So what is wrong with my code? How can I bind complex data that has list of complex type object properties?

Change the models so that you get a direct relationship between the Content and the associated File (the Order property is unnecessary)
public class CreateBlogVM
{
[Required]
[MaxLength(250)]
public string Title { get; set; }
public List<FileVM> Files { get; set; }
}
public class FileVM
{
[Required]
public string Content { get; set; }
[Required]
public HttpPostedFileBase Image { get; set; }
}
You can only append simple name/value pairs to FormData (not arrays of objects). In your loop, append the data using
var fd = new FormData();
fd.append('Title', $scope.title);
for (var i=0; i<$scope.rows.length; i++)
{
....
var content = $scope.rows[i].value;
fd.append('Files[' + i + '].Content', content);
....
var file = $scope.rows[i].file;
fd.append('Files[' + i + '].Image', file);
}
....
so that your generating the names with indexers that relate to your collection property (Files[0].Content, Files[0].Image, Files[1].Content etc)
Then your POST method signature will be
public JsonResult Create(CreateBlogVM model)

Related

.Net Core Web API not deserializing JSON from angularJS

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.

API returning Complex JSON Which Needs to Map TypeScript Class

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;//

Why Database Storing [object object] type

I'm trying to uplode File along with some columns.File Uploading sucessfully but why Remaining column taking [object object] type
Table
public partial class CreateUserIdentity
{
public int Id { get; set; }
public string Email { get; set; }
public string Image { get; set; }
public string password { get; set; }
}
AngularJs
var description = {
Passwoed: $scope.FileDescription,
Email : $scope.Email
}
FileUploadService.UploadFile($scope.SelectedFileForUpload, description
).then(function (d) {
});
Mvc Controller
[HttpPost]
public JsonResult SaveFiles(string description)
{
if (Request.Files != null)
{
CreateUserIdentity f = new CreateUserIdentity
{
Image = actualFileName,
Email = description,
};
using (ProjectsEntities dc = new ProjectsEntities())
{
dc.CreateUserIdentities.Add(f);
dc.SaveChanges();
Message = "File uploaded successfully";
flag = true;
}
}
return new JsonResult { Data = new { Message = Message, Status = flag } };
The description you are sending is not a string - it is an object consisting of Email and Passwoed - type that correctly in your rest-controller and use Email = description.Email
Alertatively you can just send $scope.Email from the angular-side instead of wrapping it in a description-Object, that should work as well (if you don't need the attribute Passwoed).

How to send an array items from UI to MVC Controller

I have an array of objects in UI that is being sent to MVC Controller . The Array of objects look like :
`DoorId`,`DoorName` and an array of `Schedules`. `Schedules` array has `ScheduleId` and `ScheduleName`.
Now how to send it to MVC Controller ? So that , every DoorId and it's associated ScheduleId can be extracted separately to form another obeject ?
Presently , I am sending the DoorId Array and the ScheduleId array separately ,
But I do not want to do that . I want to send the entire array itself.
public async Task<ActionResult> AddGroup(string[] DoorIds, string[] scheduleEntity)//AccessGroupEntity entity, string accountId
{
GroupEntity groupEntity = new GroupEntity();
var doorScheduleList = new List<DoorInfoEntity>();
for(int i=0;i< DoorIds.Length;i++)
{
doorScheduleList.Add(new DoorInfoEntity()
{
DoorId = DoorIds[i],
ScheduleId = scheduleEntity[i]
});
}
accessGroupEntity.DoorItems = doorScheduleList;
And then Parse it as Doors[index].DoorId and Doors[index].ScheduleId to form 'DoorInfoEntity` object.
How to do it ?
I tried object[] Doors but it says Object does not contain a definition for DoorId or ScheduleId.
You need an object graph in C# that the modelbinder can bind the posted data to which mimics the object graph you have in your JavaScript. For example:
public class Door
{
public Guid DoorId { get; set; }
public string DoorName { get; set; }
public List<Schedule> Schedules { get; set; }
...
}
public class Schedule
{
public Guid ScheduleId { get; set; }
...
}
Then, you accept the root class as a param in your action:
public async Task<ActionResult> AddGroup(List<Door> doors)
The modelbinder, then, will create the object graph server-side from the posted data.

Send IEnumerable from web api controller to angular js

I'm trying to send a IEnumerable from a web api controller to a AngularJs controller.
The code I was using was
Web Api:
readonly InventoryEntities _db = new InventoryEntities();
public IEnumerable<FDVOEligibilityRequest> Get()
{
return _db.FDVOEligibilityRequests.AsEnumerable();
}
AngularJS:
//get all customer information
$http.get("/api/Customer/").success(function (data) {
$scope.requests = data;
$scope.loading = false;
})
.error(function () {
$scope.error = "An Error has occured while loading posts!";
$scope.loading = false;
});
This worked fine, but now I'm using linq to include related tables and it doesn't work. The angularJs code is the same.
What am I doing wrong?
readonly InventoryEntities _db = new InventoryEntities();
public IEnumerable<FDVOEligibilityRequest> Get()
{
return _db.FDVOEligibilityRequests
.Include("FDVOEligibilityRequestMandatoryField")
.Include("FDVOEligibilityRequestDCCField").AsEnumerable();
}
I do get the data I want in the controller, but when i try to send it back to angular, I get a 500 (Internal Server Error) angular.js:10722
This is what FDVOEligibilityRequest looks like in the new Web Api controller
public partial class FDVOEligibilityRequest
{
public int Id { get; set; }
public string TestName { get; set; }
public string GroupName { get; set; }
public int MandatoryFieldsID { get; set; }
public int DCCFieldsID { get; set; }
public virtual FDVOEligibilityRequestMandatoryField FDVOEligibilityRequestMandatoryField { get; set; }
public virtual FDVOEligibilityRequestDCCField FDVOEligibilityRequestDCCField { get; set; }
}
If it's 500 and happens after you successfully make a return from your action then it can be an exception during serialization.
I would suggest to check that there are no circular references between FDVOEligibilityRequest and FDVOEligibilityRequestMandatoryField/FDVOEligibilityRequestDCCField.
Or try to diagnose it using code from here:
string Serialize<T>(MediaTypeFormatter formatter, T value)
{
// Create a dummy HTTP Content.
Stream stream = new MemoryStream();
var content = new StreamContent(stream);
/// Serialize the object.
formatter.WriteToStreamAsync(typeof(T), value, stream, content, null).Wait();
// Read the serialized string.
stream.Position = 0;
return content.ReadAsStringAsync().Result;
}
try {
var val = _db.FDVOEligibilityRequests
.Include("FDVOEligibilityRequestMandatoryField")
.Include("FDVOEligibilityRequestDCCField").AsEnumerable();
var str = Serialize(new JsonMediaTypeFormatter(), val);
}
catch (Exception x)
{ // break point here
}
ps: the common suggestion in this case is to use DTOs instead of EF objects with something like Automapper.

Resources