I am creating a form to put data in a database. Now I want to use an array, because I want to put a form, inside a form. For example, you have a form where a user puts information about himself, like age name and so on. One question is about his friends, you must fill in the name and age off all your friends. Because you have more then one friend, you can add more than one friend. So you fill in the name and age of one friend, click on save, and then you can continue fill in the info of another friend. When you click save, you don't save the whole form.
I hope this I explaned it well.
This is my form:
public class User
{
[BsonElement("Id")]
public int Id { get; set; }
[BsonElement("Email")]
public string Email { get; set; }
[BsonElement("Name")]
public string Name{ get; set; }
[BsonElement("Company")]
public string Company { get; set; }
[BsonElement("Array friends")]
}
View:
#model ISPInvoice.Models.Invoice
#using (Html.BeginForm("Edit", "Home"))
{
#Html.ValidationSummary(true)
<fieldset>
<legend>New Note</legend>
<h3>New Note</h3>
<div class="editor-label">
#Html.LabelFor(model => model.InvoiceNumber)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.InvoiceNumber)
</div>
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
}
Add another class called Friend
public class Friend
{
[BsonElement("Name")]
public string Name { get; set; }
[BsonElement("Age")]
public int Age{ get; set; }
}
In your User class add a List of Friends.
public class User
{
[BsonElement("Id")]
public int Id { get; set; }
[BsonElement("Email")]
public string Email { get; set; }
[BsonElement("Name")]
public string Name{ get; set; }
[BsonElement("Company")]
public string Company { get; set; }
[BsonElement("Array friends")]
public List<Friend> Friends { get;set;}
}
In order to access the Friends list inside the View, you can loop through the list.
#foreach (namespace.Models.Friend friend in Model.Friends)
{
#Html.LabelFor(item => friend.Name)
}
It sounds like you'll need some client-side code.
What I'd probably do, as I understand your need, would be to have a hidden input in the form that you update with the friends that have been selected.
The trouble is that standard form encoding doesn't support hierarchical data, so you need to add support for that manually. That's not too hard, as you'll see, but it's a hassle. This would be super easy if you were using a framework like Angular, or even just posting with AJAX in general. But that's a pretty big change, that might not be worth going through and implementing just for this case.
This example depends on jQuery. It's also super simple, and doesn't support anything like removal or editing of existing friends. You should be able to add that stuff pretty easily, though. It also does absolutely no validation on anything, so take that how you will.
var friends = [];
function refreshHiddenField() {
$("#friendsListText").val(friends.map(function(d) { return d.name + "=" + d.age }).join(";"));
}
document.addEventListener("DOMContentLoaded", function(event) {
$("#btnAddFriend").click(function() {
var name = $("#newFriendName").val();
var age = $("#newFriendAge").val();
friends.push({ name : name, age: age });
refreshHiddenField();
var li = $("<li />").text(name + " (" + age + ")");
$("#friendsList").append(li);
});
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<!-- This will actually be an #Html.HiddenFor(m => m.FriendsText), or something -->
<input type="text" id="friendsListText" readonly placeholder="Add some friends first" />
<hr />
<input type="text" id="newFriendName" placeholder="Name" />
<input type="number" id="newFriendAge" placeholder="Age" />
<button type="button" id="btnAddFriend">Add!</button>
<br />
<ul id="friendsList">
</ul>
As you'll find playing with that (albeit very lightly tested) example, adding a friend appends the details to a user-visible list, and appends a machine-readable format into an input. Per the comment, this input is visible in this example to see what's happening, but it wouldn't be in production.
Once you've got your client-side code done, you just have to parse the hidden field on save. Assuming a Friend class with properties for string Name and int Age,
[HttpPost]
public ActionResult EditProfile(User m)
{
// Unrelated stuff
var friends = m.FriendsText.Split(';').Select(c => {
var args = c.Split('=');
return new Friend { Name = args[0], Age = int.Parse(args[1]) };
})
// Use the collection
// Unrelated stuff
}
This is just some pretty simple string manipulation with LINQ and the Split function to understand the machine-readable format from the client.
Note with this implementation that names can't have semicolons or equals signs in them.
Once you've got it all in, printing it out is easy. Just, yes, add a collection type to your model, then loop through with a foreach loop.
<ul>
#foreach (var f in Model.FriendsCollection)
{
<li>#f.Name (#f.Age)</li>
}
</ul>
I know that's not what you're talking about, but I offer this just as an example of how you would include a collection in the model, per your original question.
Related
I have a index.razor, that has a for loop that generates let's say 100 components (those display text, and a couple more vlaues), now i want to hava a reference of every component, is there a way to save those in a List or an array?
I tryed it myself in many ways but I get the name of the component displayed in the html.
addPdfComp is executed by a button:
public void addPdfComp(int type)
{
var newComponent = new PdfComp();
newComponent.text = "some test text";
PdfComponentsList.Add(newComponent);
}
and to display the components:
#foreach (var comp in PdfComponentsList)
{
<div>#comp</div>
}
result:
see result
Edit:
The component itself:
<div class="row p-1 " style="border:solid; ">
<h5>TextComponent</h5>
<div>
<label>X</label>
<input type="number" class="col-2" #bind-value="_x"/>
<label>Y</label>
<input type="number" class="col-2" #bind-value="_y"/>
<label>Text</label>
<input type="text" class="col-4" #bind-value="text" />
</div>
#code {
[Parameter]
public string text { get; set; } = "Missing text";
[Parameter]
public string type { get; set; } = "noone";
[Parameter]
public int _x { get; set; } = 0;
[Parameter]
public int _y { get; set; } = 0;
}
The default string representation for an object is the fully qualified class name for that object, which is what you're seeing. This means that whatever PdfComp is, it doesn't have a meaningful .ToString() implementation.
And that's all Razor syntax really does in this case, it just emits output as a string. So #something will basically evaluate something as a string and write it to the output.
Based on a comment above:
My intention is [...] to display the component itself as if i just typed: <PdfComp></PdfComp> 100 times.
An instance of PdfComp in C# code is not the same thing as an instance of <PdfComp></PdfComp> in the markup. (Though I imagine they are very closely analogous somewhere in the depths of the framework.) What you want isn't a list of object instances, but a list of the data your view needs to render the markup.
So, structurally, more like this:
// PdfComponentsList here is a list of strings
PdfComponentsList.Add("some test text");
And then loop through it in the markup to display the elements:
#foreach (var pdf in PdfComponentsList)
{
<PdfComp text="#pdf"></PdfComp>
}
You could also make PdfComponentsList a list of some object to hold multiple properties, for example:
PdfComponentsList.Add(new SomeObject { Text = "some test text" });
And in the markup:
#foreach (var pdf in PdfComponentsList)
{
<PdfComp text="#pdf.Text"></PdfComp>
}
In my razor page PageModel I have a bound List<T> property:
[BindProperty]
public List<JobCard> CustomerSpecificJobCards { get; set; }
Here is the JobCard model:
public partial class JobCard
{
public int JobCardId { get; set; }
public string Products { get; set; }
public string CusCode { get; set; }
public string Category { get; set; }
public bool IsAssigned { get; set; }
}
The list of JobCards is populated after the user posts an order number from the page:
CustomerSpecificJobCards = AllJobCards.Where(jc => jc.CusCode == WorkOrderInfo.MarkForCusCode).ToList();
It is then displayed in a form on the razor page via a foreach loop:
#foreach (var card in Model.CustomerSpecificJobCards)
{
<div class="col-1"><input asp-for="#card.IsAssigned" /></div>
<div class="col-9">#card.Products</div>
<div class="col-2">#card.JobCardId</div>
}
Users are shown a list of job cards and a checkbox. Once checked the user submits the selections. When I look at the CustomerSpecificJobCards that are posted, the list is empty. Why? Based on information here, I decided to change the foreach loop to a for loop:
#for (var card = 0; card < Model.CustomerSpecificJobCards.Count; card++)
{
<div class="col-1"><input asp-for="#Model.CustomerSpecificJobCards[card].IsAssigned" /></div>
<div class="col-9">#Model.CustomerSpecificJobCards[card].Products</div>
<div class="col-2">#Model.CustomerSpecificJobCards[card].JobCardId</div>
}
[EDIT] Originally, I thought all the values were returned using the for loop, but it turns out only the .IsAssigned values are returned... Products and JobCardId are empty. I'm using Razor Pages for the first time. What am I doing wrong?
[Followup] After reading Rafalon's answer, I found this explanation of binding a complex collection with checkboxes in either a for or foreach loop. Plus, here is another excellent related link on data binding.
Short answer: check the generated HTML for both for and foreach and you'll see that with foreach, you can not expect the form to return the List correctly (with asp-for helper).
for is needed so you get indexable inputs, looking like:
<input name='CustomerSpecificJobCards[0].IsAssigned'
id='CustomerSpecificJobCards_0__IsAssigned' />
You could still do it with foreach, but without asp-for it's quite tedious, and your model have to meet some requirements (having an index property for example).
Your other problem comes from the fact that #Model.CustomerSpecificJobCards[card].Products is text only.
Therefore you should either replace it with an input, just like IsAssigned, or else you can add a hidden input:
<input type="hidden" asp-for="Model.CustomerSpecificJobCards[card].Products" />
Same goes for JobCardId.
I am trying to understand how I should validate on the client sections of my MVC3 page independently and have come up with a simplyfied version of what I am trying to achieve.
If I use one form:
Pros: When I submit back to the "PostData" controller method I receive all data contained within the form. In this case both values "name" and "description", which means that I can instantiate "PersonHobbyModel" and assign the data I have received. I can either store in the database or I can return the same view.
Cons: I cant validate independently. So if "name" isn't completed and I complete "description" I can still submit the page. (This is a simplyfied version of what I am trying to do and I would have more fields than just "name" and "description")
With two forms:
Pros: I can validate independently.
Cons: The controller method only receives the subitted forms data which, in this case either "Persons name" or "Hobby description" which means that I can't recreate a full instance of "PersonHobbyModel".
This is the model:
public class Person {
[Display(Name = "Person name:")]
[Required(ErrorMessage = "Person name required.")]
public string Name { get; set; }
}
public class Hobby {
[Display(Name = "Hobby description:")]
[Required(ErrorMessage = "Hobby description required.")]
public string Description { get; set; }
}
public class PersonHobbyModel {
public PersonHobbyModel() {
this.Person = new Person();
this.Hobby = new Hobby();
}
public Person Person { get; set; }
public Hobby Hobby { get; set; }
}
This is the controller:
public class PersonHobbyController : Controller
{
//
// GET: /PersonHobby/
public ActionResult Index()
{
var model = new PersonHobbyModel();
return View(model);
}
public ActionResult PostData(FormCollection data) {
var model = new PersonHobbyModel();
TryUpdateModel(model.Person, "Person");
TryUpdateModel(model.Hobby,"Hobby");
return View("Index", model);
}
}
This is the view:
#model MultipleFORMStest.PersonHobbyModel
#{
ViewBag.Title = "Index";
}
<h2>
Index</h2>
#using (Html.BeginForm("PostData", "PersonHobby")) {
<div>
#Html.LabelFor(model => model.Person.Name)
#Html.TextBoxFor(model => model.Person.Name)
#Html.ValidationMessageFor(model => model.Person.Name)
<input type="submit" value="Submit person" />
</div>
}
#using (Html.BeginForm("PostData", "PersonHobby")) {
<div>
#Html.LabelFor(model => model.Hobby.Description)
#Html.TextBoxFor(model => model.Hobby.Description)
#Html.ValidationMessageFor(model => model.Hobby.Description)
<input type="submit" value="Submit hobby" />
</div>
}
UPDATE 1
I didnt mention, as I wanted to keep the question as simple as possible, but for one of the sections I am using "jquery ui dialog". I initially used a DIV to define the dialog, which I had inside my main form. This would of caused one problem as I wouldn't have been able to validate on the client the "JQuery dialog form" independently from the rest of the form.
Saying this jquery did removed the "div jquery ui dialog" from the main form which made me include the dialog in it's own form. For this reason I have ended up with two forms. The advantage is that I can now independently validate the "jquery dialog ui form".
But I am confused as to how should I handle on the server data submited from various forms on the client as there is a chance that the user has JS disabled. If I submit from one form I can't access the data in other forms.
UPDATE 2
Thanks for the replies. I believe I do need two forms and two entities as I want to validate them independently on the client, (apart from being kind of forced to by "Jquery UI Dialog"). For instance if I have, instead of one hobby I have a list of hobbies, which I could posible display in a grid in the same view. So I could not fill in the person name, but continue to add hobbies to the grid, If I do not complete the hobby description I'd get a validation error. (Sorry as I should of included both of my updates in the initial question but for the purpose of clarity I wanted to keep it as simple as posible)
From my perspective, you have a single view model that corresponds to two entity models. In your place I would use a single form and validate the view model and not really think about it as two (dependent) entities. Receive back the view model in your action, instead of a generic form collection, and use model-based validation via data annotation attributes. Once you have a valid, posted model you can then translate that into the appropriate entities and save it to the database.
Model
public class PersonHobbyViewModel {
[Display(Name = "Person name:")]
[Required(ErrorMessage = "Person name required.")]
public string Name { get; set; }
[Display(Name = "Hobby description:")]
[Required(ErrorMessage = "Hobby description required.")]
public string Description { get; set; }
}
Controller
public class PersonHobbyController : Controller
{
//
// GET: /PersonHobby/
[HttpGet] // mark as accepting only GET
public ActionResult Create() // Index should probably provide some summary of people and hobbies
{
var model = new PersonHobbyViewModel();
return View(model);
}
[HttpPost] // mark as accepting only POST
public ActionResult Create(PersonHobbyViewModel model) {
if (ModelState.IsValid) {
var person = new Person { Name = model.Name };
var hobby = new Hobby { Description = model.Description };
person.Hobbies = new List<Hobby> { hobby };
db.Persons.Add( person );
db.SaveChanges();
}
return RedirectToAction( "details", new { id = person.Id } ); // view the newly created entity
}
}
View
#model MultipleFORMStest.PersonHobbyViewModel
#{
ViewBag.Title = "Create";
}
<h2>
Create</h2>
#using (Html.BeginForm("Create", "PersonHobby")) {
<div>
#Html.LabelFor(model => model.Person.Name)
#Html.TextBoxFor(model => model.Person.Name)
#Html.ValidationMessageFor(model => model.Person.Name)
<input type="submit" value="Submit person" />
</div>
<div>
#Html.LabelFor(model => model.Hobby.Description)
#Html.TextBoxFor(model => model.Hobby.Description)
#Html.ValidationMessageFor(model => model.Hobby.Description)
<input type="submit" value="Submit hobby" />
</div>
}
I think your ViewModel should be only only specific to that view you are representing. In this case, i would use a ViewModel like this
public class AddPersonHobbyViewModel
{
[Required]
[Display (Name="Person Name")]
public string PersonName { set;get;}
[Required]
[Display (Name="Hobby Description")]
public string HobbyDescription { set;get;}
}
And in my PostData ActionMethod, I will check for Model Validation
[HttpPost]
public ActionResult PostData(AddPersonHobbyViewModel objVM)
{
if(ModelState.IsValid)
{
// Everything is fine. Lets save and redirect to another get View( for PRG pattern)
}
return View(objVm);
}
And you use only one Form in your View which is strongly typed to AddPersonHobbyViewModel
#model AddPersonHobbyViewModel
#using (Html.BeginForm("PostData","Person"))
{
#Html.TextBoxFor(m=>m.PersonName)
#Html.ValidationMessageFor(m => m.PersonName)
#Html.TextBoxFor(m=>m.HobbyDescription )
#Html.ValidationMessageFor(m => m.HobbyDescription )
<input type="submit" value="Save" />
}
I have a problem in my application
I am using MVC 3 with razor and i want to get the value of checked box in the form
#using (Html.BeginForm("ConfirmItemAvilabilty", "Order", FormMethod.Post, new { id = "frmConfirmAvilabilty", name = "frmConfirmAvilability" }))
{
foreach (OrderItem orderItem in orderAction.Order.OrderItems)
{
<div class="product">
<ul>
<li>
<div class="productImg">
<img src="#orderItem.Product.Image" width="100px" height="100px"/>
</div>
<div class="centered">
Link <span>#orderItem.Product.TitleAr</span>
<span>#orderItem.Product.Price</span>
</div>
#if (currentUser.UserTypeEnum == UserTypeEnum.Reseller)
{
<div>
#Html.CheckBox("ChkConfirm", orderItem.IsAvailable, new { id="chkConfirm" ,#class="chkConfirm"})
#Html.Hidden("OrderItemId", orderItem.Id, new { id="hdnConfirm"})
</div>
}
</li>
</ul>
</div>
}
if (currentUser.UserTypeEnum == UserTypeEnum.Reseller)
{
<button>Confirm</button>
}
}
Simply i want to get the value of all checked checkboxs i tryed to create a model holding the value of my checkbox and the value of the hidden text below it
public class ItemOrderModel
{
public string ChkConfirm { get; set; }
public string OrderItemId { get; set; }
}
and in my controller i do the following but nothing happened
public ActionResult ConfirmItemAvilabilty(List<ItemOrderModel> OrderItems)
{
return View();
}
but orderItems always returns null, Can anyone help me in that?
----------------- Edit ------------------
Thank you Sam and Jesse
I Found a solution for my problem but I am facing another problem now first of all i solved my problem by changing in model view to be like that
public class ItemOrderModel
{
public List<bool> ChkConfirms { get; set; }
public List<string> OrderItemId { get; set; }
}
and change the checkbox name to be
#Html.CheckBox("ChkConfirms", orderItem.IsAvailable, new { id = "chkConfirm", #class = "chkConfirm" })
the problem now is
when i submit i found two values false that is the actual representation of my checkboxs and two ids that's also the actual representation of my hidden fields "Correct scenario"
when i check one of check boxs in the same form i found 3 result for the check box and 2 results for the hidden field Can any one help in that or have a solution
You need to look into Editor Templates:
How to create custom editor/display templates in ASP.NET MVC 3?
These allow you to do the exact thing you are talking about.
This seems like it should be prettty easy - but I just can't get it to work!
I have an enum in my model, which I want to display as a list of checkboxes. The user can select multiple checkboxes, and I want to save this in the database.
So the enum is like so (approx 20 elements unabridged):
public enum ReferrerType
{
[Description("No Data")]
NoData = 9999,
[Description("Father or Mother")]
Parents = 1,
[Description("Brother or Sister")]
Sibling = 2,
[Description("Other")]
Other = 10
}
Whereby the Description is what is shown on the UI, and the numeric value is what is to be saved in the database. The numbers have to remain as listed, as they go directly into a stats package.
I then defined a Referrer class:
public class Referrer
{
public virtual Guid Id { get; private set; }
public virtual ReferrerType{ get; set; }
}
I realise this might be an odd (anti)pattern. I developed it in haste, and am repenting at leisure. Any advice on improving this model would also be much appreciated!
My controller sets up the list:
private static IList<string> GenerateReferrerList()
{
var values = from ReferrerType e in Enum.GetValues(typeof(ReferrerType))
select new { Name = e.ToDescription() };
return values.Select(x => x.Name).ToList();
}
And I use it in my View like this:
<div class="radio-boolean form-field" id="Referrers">
<p class="form-field-attribute"> <span class="label">Referred By </span> </p>
<% for (var i = 0; i < ((IList<string>)ViewData["ReferrerList"]).Count; i++)
{ %>
<p class="form-field-value">
<%= Html.CheckBox(string.Format("Referrers[{0}].Type", i) ) %>
<label for="Referrers"> <%= ((IList<string>)ViewData["ReferrerList"])[i]%></label>
</p>
</div>
And it doesn't work! I guess I'm missing something obvious, but I can't work out what. There are no errors - just an empty database table where referrers should be...
As always, any help much appreciated!
Let's take a moment and see what do we need here. We need to show a form which will contain multiple checkboxes (one for each value of the enum) and an associated label (this label should come from the Description attribute use on the enum). When this form is submitted we want to fetch all the values that the use has checked.
So as always once we have clear definition of what we are trying to do we introduce our view model:
public class MyViewModel
{
public bool IsChecked { get; set; }
public ReferrerType ReferrerType { get; set; }
public string Text { get; set; }
}
Then we write a controller:
public class HomeController : Controller
{
public ActionResult Index()
{
var model = Enum.GetValues(typeof(ReferrerType)).Cast<ReferrerType>().Select(x => new MyViewModel
{
ReferrerType = x,
Text = x.ToDescription() // I guess that's an extension method you wrote
});
return View(model);
}
[HttpPost]
public ActionResult Index(IEnumerable<MyViewModel> model)
{
...
}
}
And finally a strongly typed view corresponding to the Index action of our controller (~/Views/Home/Index.aspx):
<% using (Html.BeginForm()) { %>
#Html.EditorForModel()
<input type="submit" value="OK" />
<% } %>
and the last part is the corresponding editor template (~/Views/Home/EditorTemplates/MyViewModel.ascx):
<%# Control
Language="C#"
Inherits="System.Web.Mvc.ViewUserControl<AppName.Models.MyViewModel>" %>
<%= Html.CheckBoxFor(x => x.IsChecked) %>
<%= Html.HiddenFor(x => x.ReferrerType) %>
<label><%: Model.Text %></label>
Now when this form is submitted inside the POST index action you would get a list of all enums with a corresponding boolean value indicating whether the user checked it or not.
OT: Don't perform excess actions:
return (from e in Enum.GetValues(typeof(ReferrerType))
select e.ToDescription()).ToList();
or just
return Enum.GetValues(typeof(ReferrerType)).Select(e => e.ToDescription()).ToList();