How to send an array items from UI to MVC Controller - arrays

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.

Related

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

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

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)

Displaying Specific Fields from Facebook Graph API JSON

I'm trying to simply display the list of members in a specific group using the Facebook Graph API. I'm using Newtonsoft.JSON.
Here is the results of my url query:
Graph API Results
I used a JSON class generator and it gave me this:
public class Datum
{
public string name { get; set; }
public string id { get; set; }
public bool administrator { get; set; }
}
public class Cursors
{
public string before { get; set; }
public string after { get; set; }
}
public class Paging
{
public Cursors cursors { get; set; }
}
public class Members
{
public List<Datum> data { get; set; }
public Paging paging { get; set; }
}
public class RootObject
{
public Members members { get; set; }
public string id { get; set; }
}
I've tried every combination I can think of to display simply the list of members in a multi-line text box, but not sure if this is even the best way to display the list on a Windows Form App.
Could someone help me understand 2 things.
1) What is the best component to display the list of names in a Windows Form App?
2) What is the 1 or 2 lines to generate just the list of names using JsonConvert.DeserializeObject from this?
My raw data is stored in: string responseFromServer = reader.ReadToEnd();
To deserialize the JSON into your classes:
RootObject obj = JsonConvert.DeserializeObject<RootObject>(responseFromServer);
To get the member names into a List<string>:
List<string> members = obj.members.data.Select(d => d.name).ToList();
Note: You need to have using System.Linq; at the top of your file in order to use the Select and ToList methods.
As far as displaying the data in a windows form app, there's not a "best" component-- it depends on what you're trying to accomplish as to what control you would choose to use. For example, if all you want to do is display the list of names in a multi-line textbox, you could do this:
textBox1.Text = string.Join("\r\n", members);
If you want to allow the user to be able to select individual names and do something based on that selection, you would probably want to use a ListBox or a ComboBox instead. You can populate a ListBox or ComboBox like this:
listBox1.DisplayMember = "name";
listBox1.DataSource = obj.members.data;
That should be enough to get you started.

Cannot save multiple files to database with Foreign Key

In my MVC application I have two tables called Ticket and Attachment and I want to save attachments for per ticket. The problem is that: I need to save multiple attachments with the TicketID when creating a new ticket. So, I think I should create a new ticket in the Ticket table and then get its ID and save all the attachments with this TicketID to the Attachment table in a loop. I have look at many web sites and stackoverflow, but there is not such a kind of problem or solution on that pages. Any idea?
Note: I use Entity Framework Code First, but I can also use Stored Procedure or SQL command for this operation.
Here are these two models:
public class Ticket
{
[Key]
public int ID { get; set; }
public string Description { get; set; }
//... removed for clarifty
}
public class Attachment
{
[Key]
public int ID { get; set; }
//Foreign key for Ticket
public int TicketID { get; set; }
public byte[] FileData { get; set; }
public string FileMimeType { get; set; }
}
And the controller method:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Exclude = null)] TicketViewModel viewModel
/* contains both model: Ticket and Attachment */, IEnumerable<HttpPostedFileBase> files)
{
if (ModelState.IsValid)
{
//??? First I need to save Ticket
repository.SaveTicket(viewModel.Ticket);
foreach(var f in files)
{
viewModel.Attachment.FileMimeType = f.ContentType;
viewModel.Attachment.FileData = new byte[f.ContentLength];
f.InputStream.Read(viewModel.Attachment.FileData , 0, f.ContentLength);
//??? Then save all attachment. But no idea how to get TicketID
repository.SaveAttachment(viewModel.Attachment);
}
}
return View();
}
The ID property will be automatically filled by EF after a SaveChanges. Your code can then use it. I assume that the viewModel.Ticket object is the actual object saved to the database. If not, please also post the SaveTicket method.
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Exclude = null)] TicketViewModel viewModel
/* contains both model: Ticket and Attachment */, IEnumerable<HttpPostedFileBase> files)
{
if (ModelState.IsValid)
{
// assumes viewModel.Ticket is the actual entity saved, and SaveChanges is called.
repository.SaveTicket(viewModel.Ticket);
foreach(var f in files)
{
viewModel.Attachment.FileMimeType = f.ContentType;
viewModel.Attachment.FileData = new byte[f.ContentLength];
f.InputStream.Read(viewModel.Attachment.FileData , 0, f.ContentLength);
// fill ticket id
viewModel.Attachment.TicketID = viewModel.Ticket.ID;
repository.SaveAttachment(viewModel.Attachment);
}
}
return View();
}
If you want to do everything in one transaction, you can add the childs immediatly, and SaveChanges will save all objects:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Exclude = null)] TicketViewModel viewModel
/* contains both model: Ticket and Attachment */, IEnumerable<HttpPostedFileBase> files)
{
if (ModelState.IsValid)
{
var ticket = viewModel.Ticket;
foreach(var f in files)
{
var attachment = new Attachment();
attachment.FileMimeType = f.ContentType;
attachment.FileData = new byte[f.ContentLength];
f.InputStream.Read(attachment.FileData , 0, f.ContentLength);
ticket.Attachments.Add(attachment);
}
// this will save the ticket and attachments
repository.SaveTicket(ticket);
}
return View();
}
Your Ticket class will have to look like this:
public class Ticket
{
[Key]
public int ID { get; set; }
public string Description { get; set; }
// EF will now to use the foreign key to the attachment table
public virtual ICollection<Attachment> Attachments { get; set; }
}

Storing search parameters

I'm currently building a website in .NET MVC 4, using Entity Framework to access SQL Server.
The website should have a complex search with multiple choices for the user, create a new search (free search), choose from the last 5 searches (history search), choose from stored search parameters.
What I'm having trouble with is the concept of saving the search parameters/sql string, because it's not sessional/cache based and should be stored somewhere (SQL Server / MongoDB / XML) I'm having the hard time in taking the most optimized path, if it's the SQL way then maybe create an entity that stores the search parameters as entities and afterward converting it into a SQL string for the search, or store it in XML and than serialize it with JSON.
Some fields of the search are not an exact db/entity match and requires summing/converting (like hours that would be calculated into certain time).
I'm more inclined to take out the best of Entity Framework abilities for the cause.
Would like to hear some expert thoughts if possible, Thank you.
Not sure if this is the "most optimized" path, but thought it seemed simple to implement:
//POCO class of item you want to search from database
public class SearchableItem
{
public string Name { get; set; }
public int Age { get; set; }
}
//MVC View Model for search page
public class SearchParamaters
{
public int? MinAge { get; set; }
public int? MaxAge { get; set; }
}
//Storable version for database
public class SavedSearchParameters : SearchParamters
{
public int SavedSearchParametersId { get; set; }
}
//Use SearchParameters from MVC, or SavedSearchParamaters from EF
public IQueryable<SearchableItem> DoSearch(SearchParamaters sp)
{
IQueryable<SearchableItem> query = db.SearchableItems;
if (sp.MinAge.HasValue) query = query.Where(x => x.Age >= sp.MinAge.Value);
if (sp.MaxAge.HasValue) query = query.Where(x => x.Age <= sp.MaxAge.Value);
return query;
}
You could also serialize the SearchParameters class as XML/JSON and save it wherever, then deserialize it and pass it to the DoSearch method as normal, then you wouldn't have to change the DB schema every time you wanted to add search parameters
EDIT: Full example using serialization
\Domain\Person.cs
namespace YourApp.Domain
{
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
}
}
\Domain\SavedPersonSearch.cs
namespace YourApp.Domain
{
//Entity object with serialized PersonSearchParameters
public class SavedPersonSearch
{
public int Id { get; set; }
public string Name { get; set; }
public string Parameters { get; set; }
}
}
\Models\PersonSearchParameters.cs
namespace YourApp.Models
{
//MVC View Model for search page
public class PersonSearchParameters
{
public int? MinAge { get; set; }
public int? MaxAge { get; set; }
}
}
\Helpers\SearchProvider.cs
using YourApp.Domain;
using YourApp.Models;
namespace YourApp.Helpers
{
public class SearchProvider
{
private YourAppDbContext _context;
public SearchProvider(YourAppDbContext context)
{
//This example uses the DbContext directly
//but you could use a Unit of Work, repository, or whatever
//design pattern you've decided on
_context = context;
}
public IQueryable<Person> SearchPersons(int savedPersonSearchId)
{
var savedSearch = _context.SavedPersonSearches.Find(savedPersonSearchId);
//deserialize (example assumes Newtonsoft.Json)
var searchParams = JsonConvert.Deserialize<PersonSearchParameters>(savedSearch.Parameters);
return SearchPersons(searchParams);
}
public IQueryable<Person> SearchPersons(PersonSearchParameters sp)
{
IQueryable<Person> query = _context.Persons;
if (sp.MinAge.HasValue) query = query.Where(x => x.Age >= sp.MinAge.Value);
if (sp.MaxAge.HasValue) query = query.Where(x => x.Age <= sp.MaxAge.Value);
return query;
}
public void SavePersonSearch(PersonSearchParameters sp, string name)
{
var savedSearch = new SavedPersonSearch { Name = name };
savedSearch.Parameters = JsonConvert.Serialize(sp);
_context.SavedPersonSearches.Add(savedSearch);
_context.SaveChanges();
}
}
}
\Controllers\PersonController.cs
namespace YourApp.Controllers
{
public class PersonsController : Controller
{
private SearchProvider _provider;
private YourAppDbContext _context;
public PersonsController()
{
_context = new YourAppDbContext();
_provider = new SearchProvider(_context);
}
//Manual search using form
// GET: /Persons/Search?minAge=25&maxAge=30
public ActionResult Search(PersonSearchParameters sp)
{
var results = _provider.SearchPersons(sp);
return View("SearchResults", results);
}
//Saved search
// GET: /Persons/SavedSearch/1
public ActionResult SavedSearch(int id)
{
var results = _provider.SearchPersons(id);
return View("SearchResults", results);
}
[HttpPost]
public ActionResult SaveMySearch(PersonSearchParameters sp, name)
{
_provider.SavePersonSearch(sp, name);
//Show success
return View();
}
}
}
conver your parameters to Base64 string. It would help you to create any hard queries for example, http://www.jobs24.co.uk/SearchResults.aspx?query=djAuMXxQUzoxMHx2MC4x&params=cXVlcnlmaWx0ZXI6 decode base64 use this service http://www.opinionatedgeek.com/DotNet/Tools/Base64Decode/default.aspx
you can also take a look on http://aws.amazon.com/cloudsearch/ it may be give you an idea about worh with parameters in your project
Something like this could work:
Store the search parameters in json/xml and persist in DB table.
1. When you want to edit the search parameters (if you even allow this), use the json/xml to pre-fill the selected parameters so user can edit criteria.
2. When user wants to run search, take parameters from json and create/run the query.
OR
Store the search parameters in json/xml and persist in DB table and also create the sql query and store the sql string (after validating parameters)
1. When you want to edit the search parameters (if you even allow this), use the json/xml to pre-fill the selected parameters so user can edit criteria.
2. When user wants to run search, simply take the saved query string and execute it.

Resources