Razor pages binding dropdownlist to complex type database not binding viewmodel - asp.net-core

I got a dropdown list that is populated from a database, It renders fine and the items are shown. The issue comes in when i try to save the model and the viewstate says its invalid for the postCategory Title and description as they are null but does have the Id value from the selection.
my db class is as follows.
public class Article
{
public long ArticleId { get; set; }
[Required]
[MaxLength(200)]
public string Title { get; set; }
public ArticleCategories PostCategory { get; set; } //this the problem
}
public class ArticleCategories
{
public long Id { get; set; }
[Required]
[MaxLength(100)]
public string Title { get; set; }
[Required]
[MaxLength(300)]
public string Description { get; set; }
public string Slug { get; set; }
public List<Article> AssociatedPosts { get; set; }
}
In my page model i load the dropdown list as follows.
public ArticleCategories NewArticleCategory { get; set; }
public List<SelectListItem> PostCategories
{
get
{
List<SelectListItem> NewList = new List<SelectListItem>();
NewList = _context.ArticleCategories.Select(a =>
new SelectListItem
{
Value = a.Id.ToString(),
Text = a.Title.ToString(),
}).ToList();
return NewList;
}
}
and on the page
<div class="form-group">
<label asp-for="BlogArticle.PostCategory" class="control-label"></label>
<select asp-for="BlogArticle.PostCategory.Id" class="form-control" asp-items="Model.PostCategories">
<option value="">--Choose a Catergory--</option>
</select>
#Html.HiddenFor(m=>m.BlogArticle.PostCategory.Title )
#Html.HiddenFor(m=>m.BlogArticle.PostCategory.Description )
<span asp-validation-for="BlogArticle.PostCategory" class="text-danger"></span>
</div>
It only select the Id so tried to attach it by retrieving it from the db.
var PostCategory = _context.ArticleCategories.Where(c => c.Id == BlogArticle.PostCategory.Id).FirstOrDefault();
if (PostCategory != null)
{
BlogArticle.PostCategory = PostCategory;
}
if (!ModelState.IsValid)
{
return Page();
}
not sure where i am going wrong, if there any advice or suggestions it would be greatly apricated. thank you in advance.

From your code, When you pass data to backend from view, the ArticleCategories model will only have Id value from selection, the values of Title and Description are null because you do not pass any value to their input tag right? modelsate will only validate the model passed from the view. Now the ArticleCategories model passed from the view only has id value, you also add [Required] tag to Title and Description properties, So Title and Description will be invalid in modelsate.
In your code, I think you want ModelSate to validate other properties, So you need to remove Title and Description properties from ModelSate, Please refer to this code :
if (ModelState.Remove("BlogArticle.PostCategory.Title") && ModelState.Remove("BlogArticle.PostCategory.Description"))
{
if(!ModelState.IsValid)
return Page();
}
return Page();

Related

Problem when submit a form with select tag helper asp.net core 6

I want to use select tag helper to choose a role for creating account in my web app.
The following is my code
[HttpGet]
public ActionResult Create()
{
var model = new AccountCreateViewModel()
{
Roles = new SelectList(_roleManager.Roles, "Id", "Name").ToList()
};
return View(model);
}
The following is the code in view of the select.
<div class="form-floating">
<select asp-for="RoleId" asp-items="#Model.Roles" class="form-select"></select>
<label asp-for="RoleId"></label>
<span asp-validation-for="RoleId" class="text-danger"></span>
</div>
<input type="submit" class="w-100 btn btn-lg btn-primary" />
The following is my model
public class AccountCreateViewModel
{
public RegisterModel.InputModel Input { get; set; } = new();
[StringLength(50)]
[Required]
public string FullName { get; set; }
[DataType(DataType.Date)]
public DateTime? BirthDate { get; set; } = null;
[StringLength(80)]
public string Address { get; set; }
[Required]
[Display(Name = "Role")]
public string RoleId { get; set; }
public List<SelectListItem> Roles { get; set; }
}
However, after I submit the form, then the controller check the model state, and it is invalid. I have debugged and all the fields is valid except Roles.
So, someone can give me a solution for this situation?
model state debugging
Apply [ValidateNever] attribute to remove validate of the Roles on the server side. When applied to a property, the validation system excludes that property.
public class AccountCreateViewModel
{
...
[ValidateNever]
public List<SelectListItem> Roles { get; set; }
}
Consider that a SelectList takes an object of type IEnumerable as the first argument in the constructor. Make sure that you give it an IEnumerable or List in this section:
Roles = new SelectList(_roleManager.Roles, "Id", "Name").ToList()
This code may help you:
Roles = new SelectList(_roleManager.Roles.ToList(), "Id", "Name")
If _roleManager.Roles returns an IEnumerable or List, you don't need the .ToList()

Save navigation property as null

I have a Job model and StatusOnHold model.
I added navigation property StatusOnHold in the Job model.
from some reason, when I'm saving the Job model with an empty StatusOnHold, I'm still getting value in the StatusOnHoldId in the Job model.
when StatusOnHold is empty, I'm trying to receive NULL value in the StatusOnHoldId in the Job model.
when StatusOnHold is not empty, I'm trying to get StatusOnHoldId and save the value in the StatusOnHold model (which it's working like that now).
Thank you so much.
Here is my Models...
public class StatusOnHoldViewModel
{
public int Id { get; set; }
public string Note { get; set; }
}
public class JobViewModel
{
[Key]
public int Id { get; set; }
public string JobNote { get; set; }
public JobStatus JobStatus { get; set; }
public CompanyViewModel Company { get; set; }
public CustomerViewModel Customer { get; set; }
public StatusOnHoldViewModel StatusOnHold { get; set; }
}
Here is the Controller...
public async Task<IActionResult> Create(JobViewModel jobViewModel)
{
if (ModelState.IsValid)
{
var job = _mapper.Map<Job>(jobViewModel);
var newjobId = await _jobRepository.AddAsync(job);
return RedirectToAction("details", new { id = newjobId });
}
return View();
}
And here is the view...
<div class="form-group row">
<label class="col-sm-2 col-form-label"></label>
<div class="col-sm-10">
<div class="m-1">On-Hold</div>
<textarea asp-for="StatusOnHold.Note" style="height:86px; min-height:86px" class="form-control" placeholder="Reason..."></textarea>
<span asp-validation-for="StatusOnHold.Note" class="text-danger"></span>
</div>
</div>
You don't have property for the StatusOnHoldViewModel the navigation property alone will not work.
So add
public int StatusOnHoldViewModelId { get; set; }
to your JobViewModel
StatusOnHoldId will not be empty and it always have data, if you do not write note in textarea, it will look like { "id":0,"note":null} on action for jobViewModel` ,this wll create a new record.
A workaround is that you could set StatusOnHold as null when note is null:
public async Task<IActionResult> Create(JobViewModel jobViewModel)
{
if (ModelState.IsValid)
{
if(jobViewModel.StatusOnHold.Note == null)
{
jobViewModel.StatusOnHold = null;
}
var job = _mapper.Map<Job>(jobViewModel);
var newjobId = await _jobRepository.AddAsync(job);
return RedirectToAction("details", new { id = newjobId });
}
return View();
}

How to pass List<model> to controller in 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)

Dropdown List MVC 4 error

I am trying to get a drop down list to work but its not working for me. This application is mainly a festival based application where you can add a festival along with your events. The error I am getting is on line:
#Html.DropDownList("towns", (IEnumerable<SelectListItem>)ViewData["Town"], new{#class = "form-control", #style="width:250px" })
This is the error I get:
There is no ViewData item of type 'IEnumerable' that has the key 'towns'.
Create.cshtml
<div class="form-group">
#Html.LabelFor(model => model.FestivalTown, new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.DropDownList("towns", (IEnumerable<SelectListItem>)ViewData["Town"], new{#class = "form-control", #style="width:250px" })
#Html.ValidationMessageFor(model => model.FestivalTown)
</div>
#*#Html.Partial("ddlFestivalCounty");*#
</div>
Controller.cshtml
//Get
List<SelectListItem> Towns = new List<SelectListItem>();
Towns.Add(new SelectListItem { Text = "Please select your Town", Value = "SelectTown" });
var towns = (from t in db.Towns select t).ToArray();
for (int i = 0; i < towns.Length; i++)
{
Towns.Add(new SelectListItem
{
Text = towns[i].Name,
Value = towns[i].Name.ToString(),
Selected = (towns[i].ID == 0)
});
}
ViewData["Town"] = Towns;
//Post
festival.FestivalTown.Town = collection["Town"];
Model.cs
public class Festival
{
public int FestivalId { get; set; }
[Required]
[Display(Name = "Festival Name"), StringLength(100)]
public string FestivalName { get; set; }
[Required]
[Display(Name = "Start Date"), DataType(DataType.Date)]
public DateTime StartDate { get; set; }
[Required]
[Display(Name = "End Date"), DataType(DataType.Date)]
public DateTime EndDate { get; set; }
[Required]
[Display(Name = "County")]
public virtual County FestivalCounty { get; set; }
[Display(Name = "Festival Location")]
public DbGeography Location { get; set; }
[Required]
[Display(Name = "Town")]
public virtual Town FestivalTown { get; set; }
[Required]
[Display(Name = "Festival Type")]
public virtual FestivalType FType { get; set; }
public UserProfile UserId { get; set; }
}
public class Town
{
public int ID { get; set; }
[Display(Name = "Town")]
public string Name { get; set; }
}
I suspect that this error occurs when you submit the form to the [HttpPost] action and not when you are rendering the form, right? And this action renders the same view containing the dropdown, right? And inside this [HttpPost] action you forgot to populate the ViewData["Town"] value the same way you did in your HttpGet action, right?
So, go ahead and populate this property the same way you did in your GET action. When you submit the form to your [HttpPost] action, only the selected value is sent to the controller. So you need to repopulate the collection values if you intend to redisplay the same view, because this view renders a dropdown which is attempting to bind its values from ViewData["Town"].
And here's what I mean in terms of code:
[HttpPost]
public ActionResult SomeAction(Festival model)
{
... bla bla bla
// don't forget to repopulate the ViewData["Town"] value the same way you did in your GET action
// if you intend to redisplay the same view, otherwise the dropdown has no way of getting
// its values
ViewData["Town"] = ... same stuff as in your GET action
return View(model);
}
And all this being said, I would more than strongly recommend you using view models instead of this ViewData/ViewBag weakly typed stuff. Not only that your code will become much more clean, but even the error messages will start making sense.

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
}
}
}