How to pass List<model> to controller in MVC 4 - asp.net-mvc-4

I have 2 model : Question and Answer such as below, I want to send a List model to View, and when submit form, i submit List model to controller, but in Action UpdateQuestion a can only get the list of question but the list of answer was not. Can you explain and show me how to get list answer of each question when i submit form
public class Question
{
[Key]
public int Id { get; set; }
[ForeignKey("QuestionType")]
public int QuestionTypeId { get; set; }
public virtual QuestionType QuestionType { get; set; }
[ForeignKey("Field")]
public int FieldId { get; set; }
public virtual Field Field { get; set; }
public string Brief { get; set; }
public bool IsGroup { get; set; }
[ForeignKey("QuestionGroup")]
public int? QuestionGroupId { get; set; }
public virtual QuestionGroup QuestionGroup { get; set; }
public int Priority { get; set; }
public int Order { get; set; }
public virtual ICollection<Answer> Answers { get; set; }
}
and:
public class Answer
{
[Key]
public Int32 Id { get; set; }
[Column(TypeName = "ntext")]
[MaxLength]
public string Content { get; set; }
[ForeignKey("Question")]
public int QuestionId { get; set; }
public virtual Question Question { get; set; }
public float Mark { get; set; }
public int Priority { get; set; }
}
I have controller Index to passing a list of Question to View:
public ActionResult Index()
{
ApplicationDbContext db = new ApplicationDbContext();
var listQuestion = db.Questions.ToList();
return View(listQuestion);
}
[HttpPost]
public ActionResult UpdateQuestion(string submit, List<Question> Questions)
{
...
return RedirectToAction("Index");
}
And In View :
#model List<Question>
#{
int i = 0;
int j = 0;
}
#using (Html.BeginForm("UpdateQuestion", "TestRoom"))
{
<ul>
#foreach(var question in Model)//Question
{
<li>
#Html.Hidden("Questions["+i+"].Id", question.Id)
#{i++;}
#Html.Raw(question.Brief)
<ul>
#foreach (var answers in question.Answers)
{
<li>#Html.RadioButton("Questions["+i+"]_Answers["+j+"]",answers.Id)
#Html.Raw(answers.Content)
#{j++;}
</li>
}
#{j = 0;}
</ul>
</li>
}
</ul>
<div class="aq-button-panel">
<button type="submit" value="Finish" name="submit"><i class="icon-pencil"></i>Submit</button>
<button type="submit" value="Back" name="submit">Go Next <i class="icon-arrow-left"></i></button>
<button type="submit" value="Next" name="submit">Go Back <i class="icon-arrow-right"></i></button>
</div>
}

There are multiple issues with you code. First you cannot bind a radio button to a complex object (in your case Answer because a radio button group only posts back a single value (in your case the id value of the selected Answer). Next you loops are generating radio buttons groups that would be attempting to bind the selected answer to only the first answer which makes no sense (your setting the value of j to 0 each time). Your model needs a property to bind to (say) int SelectedAnswer.
Start by creating view models that represent what you want to display/edit in your view (add display and validation attributes as required)
public class AnswerVM
{
public int ID { get; set; }
public string Content { get; set; }
}
public class QuestionVM
{
public int ID { get; set; }
public string Brief { get; set; }
public int SelectedAnswer { get; set; }
public IEnumerable<AnswerVM> PossibleAnswers { get; set; }
}
In your get method, get your data models and map then to the view models and return IEnumerable<QuestionVM> to the view.
Next create an EditorTemplate for typeof QuestionVM (/Views/Shared/EditorTemplates/QuestionVM.cshtml)
#model QuestionVM
<li>
#Html.HiddenFor(m => m.ID)
#Html.DisplayFor(m => m.Brief)
<ul>
#foreach(var answer in Model.PossibleAnswers)
{
<li>
<label>
#Html.RadioButtonFor(m => m.SelectedAnswer, answer.ID, new { id = "" })
<span>#answer.Content</span>
</label>
</li>
}
</ul>
</li>
and in the main view
#model IEnumerable<QuestionVM>
....
#Html.BeginForm(...))
{
<ul>
#Html.EditorFor(m => m) // this will generate the correct html for each question in the collection
</ul>
<div class="aq-button-panel">
<button type="submit" ... />
...
</div>
}
and change the POST method to
[HttpPost]
public ActionResult UpdateQuestion(string submit, IEnumerable<QuestionVM> model)
The model now contains the ID of each question and the ID of the selected answer for each question.
Note that if you need to return the view because ModelState is invalid, you will need to repopulate the PossibleAnswers property of each question (your not generating a form control for each property of each Answer in each Question - and nor should you) so the PossibleAnswers property will be an empty collection when you submit the form)

Related

Get lastest record from section using mvc5

I want to show in my home page: All section (all link category and one lastest news record in this section).
Please help me to complete my code.
Thank you so much.
My DbContext class:
public partial class xxDbContext : DbContext
{
public xxDbContext()
: base("name=xxDbConnection") { }
public virtual DbSet<Category> Categories { get; set; }
public virtual DbSet<Section> Sections { get; set; }
public virtual DbSet<News> News { get; set; }
}
public partial class Section
{
public int Id { get; set; }
public string Name { get; set; }
public virtual List<Category> Categories { get; set; }
}
public partial class Category
{
public int Id { get; set; }
public int SectionId { get; set; }
public string Name { get; set; }
public virtual Section Section { get; set; }
}
public partial class News
{
public int Id { get; set; }
public int CateId { get; set; }
public string Title { get; set; }
public string Content { get; set; }
}
My controllers
public ActionResult NewsInSec()
{
var model = db.Sections.Where(m => m.Publish).ToList();
return PartialView("NewsInSec", model);
}
My view
#model IEnumerable<xx.Models.Section>
<div>
#foreach (var sect in Model)
{
<ol class="breadcrumb">
<li>#sect.Name</li>
#foreach (var cate in sect.Categories)
{
<li>#cate.Name</li>
}
</ol>
**foreach (var item in sect.Categories.SelectMany(c => c.News).Where(c => c.Publish).OrderByDescending(c => c.CreateDate).Take(4).ToList())
{
<div>
#* News title*#
<h4>#item.Title</h4>
<img src="~/img/news/#item.Image" />
#*Content of lastest news*#
<p>#item.NewsContent</p>
<p>#item.Title</p>
</div>
}**
}
Finally, i want to show section, cate, news as my attached photo.
Please help me to see and fix my code above one more time? Thanks and thanks so much.
You can add navigation property in Category for easy access to News.
public partial class Category
{
public int Id { get; set; }
public int SectionId { get; set; }
...
public virtual List<News> News { get; set; }
}
And select last news for section:
#foreach (var cate in sect.SelectMany(s=>s.Categories.SelectMany(c=>c.News))
.OrderByDescending(n=>n.ID).Take(5))
{
<div>
// Title of lastest news
<h3></h3>
<img src="~/img/...." />
// Content of lastest news
<p></p>
</div>
}
Note: more correct way find last news in your Controller and include result in ViewModel, Like that:
public class SomeViewModel
{
public IEnumerable<Section> Sections {get;set;}
public IEnumerable<News> LastNews{get;set;}
}
Fill this model in controller and pass in view.

Encrypted Id is not retaining back in controller

I am encrypting id to hide the raw id in query string and passing it to the controller. But the Id is not retaining back in the postback to the controller.
for eg
/Vendor/EditVendor/mELirpUhRYksFj7k8-XBcQ%3d%3d
DecryptLong() method will decrypt the above id string mELirpUhRYksFj7k8-XBcQ%3d%3d to 1
controller
public ActionResult EditVendor(string id)
{
var vendor = _vendorService.GetVendorById(id.DecryptLong());
return View(vendor);
}
[HttpPost]
public ActionResult EditVendor(Vendor vendor)
{
if (ModelState.IsValid)
{
vendor.Id -- it is always zero and not retaining back
_vendorService.EditVendor(vendor);
}
return View(vendor);
}
In view
#model Eclatech.KidsHub.Objects.Vendor
#{
ViewBag.Title = "EditVendor";
Layout = "~/Views/Shared/_Layout.cshtml";
}
<h2>Edit Vendor</h2>
#using(Html.BeginForm("EditVendor","Vendor",FormMethod.Post, new Dictionary<string, object>
{
{"class","form-horizontal"},
{"role","form"}
}))
{
<div class="form-group">
#Html.LabelFor(m => m.VendorName, new Dictionary<string, object>
{
{"class","col-sm-2 control-label"}
})
<div class="col-sm-10">
#Html.TextBoxFor(m => m.VendorName,new Dictionary<string, object>
{
{"class","form-control"}
})
</div>
</div>
#Html.HiddenFor(m => m.Id)
<input type="submit" class="btn btn-primary btn-default" value="Save" />
}
Model
public class Vendor : AuditableEntity<long>
{
public string VendorName { get; set; }
public virtual ICollection<Product> Products { get; set; }
}
public abstract class AuditableEntity<T> : Entity<T>, IAuditableEntity
{
[ScaffoldColumn(false)]
public DateTime CreatedDate { get; set; }
[MaxLength(256)]
[ScaffoldColumn(false)]
public string CreatedBy { get; set; }
[ScaffoldColumn(false)]
public DateTime UpdatedDate { get; set; }
[MaxLength(256)]
[ScaffoldColumn(false)]
public string UpdatedBy { get; set; }
}
public abstract class Entity<T> : BaseEntity, IEntity<T>
{
private static long _rowNumber;
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public virtual T Id { get; set; }
[NotMapped]
public virtual long RowNumber
{
get { return ++_rowNumber; }
}
}
The problem is that your parameter name for the EditVendor method is named id and you are returning a model that also has a property named id. When you call the EditVendor method, the value of the parameter is added to ModelState which overrides the value of property Vendor.Id. If you inspect the html generated by #Html.HiddenFor(m => m.Id) you will see that the value of the input is mELirpUhRYksFj7k8-XBcQ%3d%3d, not the value returned by DecryptLong(). When this posts back, it cannot be bound to type int so Id has its default value of zero.
You can test this by adding ModelState.Clear(); before calling GetVendorById(). This will clear the value of Id and the hidden inputs value will now be 1. To solve the problem, change the name of the parameter, for example
public ActionResult EditVendor(string vendorID)
{
var vendor = _vendorService.GetVendorById(vendorID.DecryptLong());
return View(vendor);
}

Why viewmodel modelstate validates navigation property of an entity?

I have a problem with simple validation during controller Create action for entity ARTICLE. I am using EF 4 database first. Entity ARTICLE is used as foreign key in entity ACTION(ACTION.ARTICLE_id). That's why code generation tool add navigation property to entity ARTICLE, even it does not make not much sense. Each time I update entities the clasess gets to form below(ARTICLE). I checked all foreign key many times again. I really dont know what to do with this error to make clean soluton, not just clearing error in controller action. Everything - even view are scaffolded.
Action:
[HttpPost]
[Authorize(Roles = "ARTICLE_ADMIN")]
public ActionResult Edit(ARTICLE article)
{
if (ModelState.IsValid)
{
article.date_modified = DateTime.Now;
string newimage = this.Request.Form["preview_image_filename"];
string oldimage = this.Request.Form["original_image_filename"];
if (newimage.NotNullOrEmpty())
{
article.preview_image = newimage;
}
else
{
article.preview_image = oldimage;
}
db.Entry(article).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
article.date_modified = DateTime.Now;
ViewBag.ARTICLE_CATEGORY_id = new SelectList(db.ARTICLE_CATEGORY, "ARTICLE_CATEGORY_id", "description", article.ARTICLE_CATEGORY_id);
ViewBag.ARTICLE_STATUS_id = new SelectList(db.ARTICLE_STATUS, "ARTICLE_STATUS_id", "description", article.ARTICLE_STATUS_id);
ViewBag.WEB_USER_id = new SelectList(db.WEB_USER, "WEB_USER_id", "login", article.WEB_USER_id);
return View(article);
}
I am using this entity model generated via code generation tool with added annotations in metadata class, it cant be more simple
public partial class ARTICLE
{
public ARTICLE()
{
this.PROGRAM_WEEK_DAY_ITEM = new HashSet<PROGRAM_WEEK_DAY_ITEM>();
this.STORAGE = new HashSet<STORAGE>();
this.SHOW = new HashSet<SHOW>();
this.ACTION = new HashSet<ACTION>();
}
public int ARTICLE_id { get; set; }
public System.DateTime date_created { get; set; }
public Nullable<System.DateTime> date_modified { get; set; }
public string title { get; set; }
public string html { get; set; }
public int WEB_USER_id { get; set; }
public int ARTICLE_STATUS_id { get; set; }
public int ARTICLE_CATEGORY_id { get; set; }
public Nullable<System.DateTime> date_published { get; set; }
public string preview_image { get; set; }
//code generation tool added those navigation props
public virtual ARTICLE_CATEGORY ARTICLE_CATEGORY { get; set; }
public virtual ARTICLE_STATUS ARTICLE_STATUS { get; set; }
public virtual WEB_USER WEB_USER { get; set; }
public virtual ICollection<PROGRAM_WEEK_DAY_ITEM> PROGRAM_WEEK_DAY_ITEM { get; set; }
public virtual ICollection<STORAGE> STORAGE { get; set; }
public virtual ICollection<SHOW> SHOW { get; set; }
//this one causes trouble I think, but no clue why
public virtual ICollection<ACTION> ACTION { get; set; }
}
metadata class - just display names and formats:
public class ARTICLE_Metadata
{
[Key]
public int ARTICLE_id { get; set; }
[Display(Name="Vytvořeno")]
public System.DateTime date_created { get; set; }
[Display(Name = "Změněno")]
public Nullable<System.DateTime> date_modified { get; set; }
[Display(Name = "Publikováno")]
[DisplayFormat(DataFormatString = "{0:MM/dd/yyyy}", ApplyFormatInEditMode = true)]
public Nullable<System.DateTime> date_published { get; set; }
[Display(Name = "Titulek článku")]
public string title { get; set; }
[Display(Name = "Obsah článku")]
[UIHint("tinymce_full"), AllowHtml]
public string html { get; set; }
[Display(Name = "Vytvořil")]
public int WEB_USER_id { get; set; }
[Display(Name = "Status")]
public int ARTICLE_STATUS_id { get; set; }
[Display(Name = "Kategorie")]
public int ARTICLE_CATEGORY_id { get; set; }
[Display(Name = "Náhledový obrázek")]
public string preview_image { get; set; }
}
and finally form in razor view:
#using (Html.BeginForm("Create", "Articles", FormMethod.Post, new { #class = "base-form" }))
{
#Html.ValidationSummary(true)
<fieldset>
<legend>Nový článek</legend>
#Html.DatePickerFor(model => model.date_published, false)
#Html.HiddenFor(model => model.WEB_USER_id)
<p class="editor-label">
#Html.LabelFor(model => model.ARTICLE_STATUS_id)
#Html.DropDownList("ARTICLE_STATUS_id")
#Html.ValidationMessageFor(model => model.ARTICLE_STATUS_id)
</p>
<p class="editor-label">
#Html.LabelFor(model => model.ARTICLE_CATEGORY_id)
#Html.DropDownList("ARTICLE_CATEGORY_id")
#Html.ValidationMessageFor(model => model.ARTICLE_CATEGORY_id)
</p>
<p class="editor-label">
#Html.LabelFor(model => model.title)
#Html.EditorFor(model => model.title)
#Html.ValidationMessageFor(model => model.title)
</p>
<div class="html-editor">
#Html.EditorFor(model => model.html)
</div>
<p>
<input type="submit" value="Vytvořit" class="submit" />
</p>
</fieldset>
}
When model validates and comes to controller action, ModelState.IsValid == false, ModelState claims error on property ACTION which is not even present in the table and not supposed to be there, it is navigation property.
Error has an exeption: The parameter conversion from type 'System.String' to type 'namespace.ACTION' failed because no type converter can convert between these types.
I tried to attach debugger view image but this web did not allowed it to me. I have other entities managed via controllers and view the same way - about 30 where this does not happen.
How can I get rid of this problem without creating extra model with the same properties but without navigation ones? Or just prevent this navigation property to be included to validation. Or it is a new microsoft nonsense feature?
Some times these weird errors in db-first comes from that the name of a navigation property in an entity, is same as the name of another entity. I myself experienced these problems sometimes, and I don't know exactly what is the reason.
Anyway, renaming that navigation property must get you rid of that weird error...

generate dropdownlist from a table in database

I'm tryng to be more precise to my previous question which can be found here, I got some nice answers but couldn't figure out how to use it in my situation Previous question
I got some nice answers but couldn't figure out how to use it in my situation.
basically I want to have registration page which contains
Email //Comes from my AspNetUser(datamodel) class, also AspNetUsers table exists in database.
UserName//Comes from my AspNetUser(datamodel) class, also AspNetUsers table exists in database.
Password//Comes from my AspNetUser(datamodel) class, also AspNetUsers table exists in database.
Role//dropdownlist, comes from Role(datamodel) class, also Roles table exists in database
In my controller I have impelmented my Register method in following way:
public class AccountController : Controller
{
//private readonly IDbContext dbContext;
//
// GET: /Account/
[HttpGet]
public ActionResult Login()
{
return View();
}
[HttpPost]
[AllowAnonymous]
public ActionResult Login(LoginModel model)
{
if(Membership.ValidateUser(model.UserName, model.Password))
{
FormsAuthentication.SetAuthCookie(model.UserName, model.RememberMe);
return RedirectToAction("Index", "Home");
}
ModelState.AddModelError("", "The user name or password provided is incorrect.");
return View(model);
}
[HttpGet]
public ActionResult Register()
{
string [] roles = Roles.GetAllRoles();
return View(roles);
}
[HttpPost]
public ActionResult Register(AspNetUser model)
{
return View();
}
}
in my get method i'm passing the roles to view and right now i'm using AspNetUser as model in View
#model Sorama.CustomAuthentiaction.Models.AspNetUser
#{
ViewBag.Title = "Register";
Layout = "~/Views/shared/_BootstrapLayout.empty.cshtml";
}
#section Styles{
<link href="#Url.Content("~/Content/bootstrap.css")" rel="stylesheet" type="text/css" />
}
<div class ="form-signin">
#using (Html.BeginForm("Login", "Account"))
{
#Html.ValidationSummary(true)
<h2 class="form-signin-heading"> Register </h2>
<div class ="input-block-level">#Html.TextBoxFor(model=>model.Email, new{#placeholder = "Email"})</div>
<div class ="input-block-level">#Html.TextBoxFor(model=>model.UserName, new{#placeholder = "UserName"})</div>
<div class ="input-block-level">#Html.PasswordFor(model=>model.Password, new{#placeholder ="Password"})</div>
<div class ="input-block-level">#Html.DropdownlistFor(.....//don't no how to generate dropdownlist)
<button class="btn btn-large btn-primary" type="submit">Sign In</button>
}
</div>
can u tell me how to get that dropdownlist and how can I pass that selected value to controller to use it so that i can put user in role during registration? Would it be better to create new model for Registration?
Edit: AspNetUser model
public class AspNetUser
{
private ICollection<Role> _roles= new Collection<Role>();
public Guid Id { get; set; }
[Required]
public virtual String Username { get; set; }
public virtual String Email { get; set; }
[Required, DataType(DataType.Password)]
public virtual String Password { get; set; }
public virtual String FirstName { get; set; }
public virtual String LastName { get; set; }
[DataType(DataType.MultilineText)]
public virtual String Comment { get; set; }
public virtual Boolean IsApproved { get; set; }
public virtual int PasswordFailuresSinceLastSuccess { get; set; }
public virtual DateTime? LastPasswordFailureDate { get; set; }
public virtual DateTime? LastActivityDate { get; set; }
public virtual DateTime? LastLockoutDate { get; set; }
public virtual DateTime? LastLoginDate { get; set; }
public virtual String ConfirmationToken { get; set; }
public virtual DateTime? CreateDate { get; set; }
public virtual Boolean IsLockedOut { get; set; }
public virtual DateTime? LastPasswordChangedDate { get; set; }
public virtual String PasswordVerificationToken { get; set; }
public virtual DateTime? PasswordVerificationTokenExpirationDate { get; set; }
public virtual ICollection<Role> Roles
{
get { return _roles; }
set { _roles = value; }
}
}
You'd better have a view model specifically designed for this view. Think of what information you need in the view and define your view model:
public class RegisterViewModel
{
public string Email { get; set; }
public string UserName { get; set; }
public string Password { get; set; }
public string SelectedRole { get; set; }
public IEnumerable<SelectListItem> Roles { get; set; }
}
As you can see from this view model, in order to have a dropdown list you need 2 properties: one scalar property that will hold the selected value and one collection property to hold the list of available values.
and then:
public ActionResult Register()
{
string [] roles = Roles.GetAllRoles();
var model = new RegisterViewModel();
model.Roles = roles.Select(r => new SelectListItem
{
Value = r,
Text = r,
});
return View(model);
}
[HttpPost]
public ActionResult Register(RegisterViewModel model)
{
// the model.SelectedRole will contain the selected value from the dropdown
// here you could perform the necessary operations in order to create your user
// based on the information stored in the view model that is passed
// NOTE: the model.Roles property will always be null because in HTML,
// a <select> element is only sending the selected value and not the entire list.
// So if you intend to redisplay the same view here instead of redirecting
// makes sure you populate this Roles collection property the same way we did
// in the GET action
return Content("Thanks for registering");
}
and finally the corresponding view:
#model RegisterViewModel
#{
ViewBag.Title = "Register";
Layout = "~/Views/shared/_BootstrapLayout.empty.cshtml";
}
#section Styles{
<link href="#Url.Content("~/Content/bootstrap.css")" rel="stylesheet" type="text/css" />
}
<div class ="form-signin">
#using (Html.BeginForm("Login", "Account"))
{
#Html.ValidationSummary(true)
<h2 class="form-signin-heading"> Register </h2>
<div class ="input-block-level">
#Html.TextBoxFor(model => model.Email, new { placeholder = "Email" })
</div>
<div class ="input-block-level">
#Html.TextBoxFor(model => model.UserName, new { placeholder = "UserName" })
</div>
<div class ="input-block-level">
#Html.PasswordFor(model => model.Password, new { placeholder = "Password" })
</div>
<div class ="input-block-level">
#Html.DropdownlistFor(model => model.SelectedRole, Model.Roles)
</div>
<button class="btn btn-large btn-primary" type="submit">Sign In</button>
}
</div>

Poll System in ASP.NET MVC

I want to Display Polling in section of My Page, I have created these POCO classes for do that :
public class Polls
{
public int Id { get; set; }
public string Question { get; set; }
public bool Active { get; set; }
public IList<PollOptions> PollOptions { get; set; }
}
public class PollOptions
{
public int Id { get; set; }
public virtual Polls Polls { get; set; }
public string Answer { get; set; }
public int Votes { get; set; }
}
And I have Used below ViewModel :
public class PollViewModel
{
public int Id { get; set; }
public string Question { get; set; }
public string Answer { get; set; }
}
Then, I passed my model using above ViewModel to my View :
public ActionResult Index()
{
var poll = from p in db.Polls
join po in db.PollOptions on p.Id equals po.Polls.Id
where p.Active == true
select new PollViewModel {
Id=p.Id,
Question=p.Question,
Answer=po.Answer
};
return View(model);
}
in my View I want to display Question and Answer of my Poll, I have tried this code :
#section Polling{
#foreach (var item in Model.Polls)
{
<input type="radio" /> #item.Answer
}
}
above code works correctly but I want to display Question too, something like this :
#section Polling{
**#Model.Polls.Question**
#foreach (var item in Model.Polls)
{
<input type="radio" /> #item.Answer
}
}
How can I do that?
PS: I have one row in my Polls Table for display in Home Page
There is relationship between Polls and PollsOption. So get Polls from your db. And pass it to view. Also you already have PollsOptions that connected to to their Polls. No need to join two tables.
controller
public ActionResult Index()
{
// get active Polls
var poll = from p in db.Poll
where p.Active == true
select p;
// pass it to the view
return View(poll);
}
view
#model IEnumerable<Polls>
#section Polling{
#foreach (var question in Model)
{
<h2>#question.Question</h2>
#foreach(var answer in question.PollOptions)
{
<input type="radio" /> #answer.Answer
}
}
}