Issue with Html.RadioButtonFor - c#

I have the follow class which I'm trying to wire up to the radio buttons
public class ProductFilter
{
public bool Tubs { get; set; }
public bool Packs { get; set; }
}
And I use this on the view as so
<div class="row col-lg-12" id="filters">
<div class="col-lg-4 text-right">
#Html.RadioButtonFor(model => model.Filter.Tubs, true) Tubs
</div>
<div class="col-lg-4"></div>
<div class="col-lg-4 text-left">
#Html.RadioButtonFor(model => model.Filter.Packs, true) Packs
</div>
</div>
Now when the page loads, and I select say tubs it posts back to the controller and I'm able to filter, when the page reloads and I select packs it posts back but the values for the both are the same, I would expect tubs to be false because I have selected packs and vice versa, can some one point out what I'm doing wrong?

It appears you want to filter based on either a value of "Tubs" or "Packs", in which case you model is wrong. You need a property that posts back either of those strings, for example
public string Filter { get; set; }
public List<string> FilterList { get; set; }
and in the controller
model.FilterList = new List<string>() { "Tubs", "Packs" };
model.Filter = "Tubs"; // set default
and in the view
#foreach(var option in Model.FilterList)
{
<label>
#Html.DropDownListFor(m => m.Filter, option, new { id = option })
<span>#option</span>
</label>
}
which will generate 2 radio button with name="Filter" and values of value="Tubs" and value="Packs", and allow selection of only one.
Alternatively you could use an enum for the filter values.

Related

ASP.NET MVC cannot pass my hidden model back to controller

model class
public int Year { get; set; } = 0;
public int Odometer { get; set; }
public string ImageURL { get; set; } = "NA";
public string Category { get; set; } = "NA";
My View
<div class="form-group">
<label class="control-label AutoLText">Has Vehicle Documents</label>
#Html.DropDownListFor(Model => Model.VehDocuments, new SelectList(Model.GetYesNo()),new { #class = "form-control AutoL" })
<span asp-validation-for="VehDocuments" class="text-danger"></span>
</div>
<div class="form-group">
#Html.HiddenFor(m => m.ImageURL)
<input type="submit" value="Add New Vehicle" class="btn MVButton_2" />
</div>
so if i dont have the "#Html.HiddenFor(m => m.ImageURL)" in my view then the "ImageURL" will not be passed to the controller.
using the "HiddenFor" is kind of a security issue ? as if they change the string it will mess-up the image path to the controller and save it to the DB.
How can i go around this ?
Don't have ImageURL in the ViewModel if the user isn't supposed to see or change it.
What you are actually asking is "How to preserve model's properties' original values when they were not edited". There are multiple options, and yes, using hidden fields is one of them. Take a look at this post on social msdn.

C# Model Binding to Custom Classes Not Working

I am having difficulty getting MVC to bind to a model I have created. I have done this quite a few times in the past successfully. As such, I am just not sure why it is not working in this project.
For example, I have the following View:
#model StoryWall.ViewModels.ViewPostViewModel
#{
ViewBag.Title = "View Post";
}
<article class="story">
<header>
<h1>#Model.story.Title</h1>
<spann class="text-muted">#Model.story.Store.StoreName</span>
<h2>Posted by #Model.story.PosterName</h2>
</header>
if(#Model.story.StoryImage != null) {
<div class="storyImageWrapper">
<img src="~/img/#Model.story.StoryImage" />
</div>
<p>#Model.story.StoryBody</p>
}
</article>
<div class="commentsSection">
<h2>Comments</h2>
<h3>Add a Comment</h3>
<form method="post" class="form-horizontal" name="CommentForm" action="/View/AddComment">
#Html.AntiForgeryToken()
<input type="hidden" name="newComment.StoryID" value="#Model.story.StoryID" />
<div class="form-group"><label class="control-label col-sm-2">Name </label><div class="col-sm-10">#Html.TextBoxFor(#m => m.newComment.CommenterName, new { #class = "form-control", #required = true, #ng_model = "CommenterName"}) <span class="text-warning" ng-show="CommentForm.newComment.CommenterName.$dirty && CommentForm.newComment.CommenterName.$invalid"> Required </span> <span class="text-warning"> #Html.ValidationMessageFor(#m => m.newComment.CommenterName) </span></div> </div>
<div class="form-group"><label class="control-label col-sm-2">Email </label><div class="col-sm-10">#Html.TextBoxFor(#m => m.newComment.CommenterEmail, new { #class = "form-control", #required = true, #ng_model = "CommenterEmail" }) <span class="text-warning" ng-show="CommentForm.newComment.CommenterEmail.$dirty && CommentForm.newComment.CommenterEmail.$invalid"> Required </span> <span class="text-warning"> #Html.ValidationMessageFor(#m => m.newComment.CommenterEmail) </span></div> </div>
<div class="form-group"><label class="control-label col-sm-2">Message </label><div class="col-sm-10">#Html.TextAreaFor(#m => m.newComment.CommentBody, new { #class = "form-control", #required = true, #ng_model = "CommentBody" }) <span class="text-warning" ng-show="CommentForm.newComment.CommentBody.$dirty && CommentForm.newComment.CommentBody.$invalid"> Required </span> <span class="text-warning"> #Html.ValidationMessageFor(#m => m.newComment.CommentBody) </span></div> </div>
<button type="submit" ng-disabled="CommentForm.$invalid">Submit</button>
</form>
<h3>Current Comments</h3>
#foreach(var comment in #Model.story.Comments) {
<blockquote>#comment.CommentBody</blockquote>
<span>Poster: #comment.CommenterName on #comment.DatePosted.ToString("MM-dd-yyyy")</span>
}
</div>
Even though I am specifically using Html.TextBoxFor() for my input boxes, the binding is still not working as expected.
This is my Controller. "comment" in the second Action method is not binding correctly; its properties are null.
public class ViewController : Controller
{
StoryModel dbContext = new StoryModel();
public ActionResult ViewPost(Int32 postID)
{
ViewPostViewModel vm = new ViewPostViewModel();
vm.story = dbContext.Stories.FirstOrDefault(s => s.StoryID == postID);
return View(vm);
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult AddComment(Comment comment)
{
if (ModelState.IsValid)
{
dbContext.Comments.Add(comment);
dbContext.SaveChanges();
return RedirectToAction("ViewPost", new { storyID = comment.StoryID});
}
else
{
ViewPostViewModel vm = new ViewPostViewModel();
vm.story = dbContext.Stories.FirstOrDefault(s => s.StoryID == comment.StoryID);
vm.newComment = comment;
return View("ViewPost", vm);
}
}
}
I know this is not the first time a similar question has been asked, but I could not find a solution that solved my problem. Additionally, as stated, this is something I have done in the past with success.
The only "new" element in this scenaria for me is Angular.js. This is my first time using the framework. Could it be interfering with the binding somehow?
If it helps, the Comment model:
public partial class Comment
{
public int CommentID { get; set; }
public int? StoryID { get; set; }
public int UserID { get; set; }
[Required]
[StringLength(75)]
public string CommenterName { get; set; }
[Required]
[StringLength(75)]
public string CommenterEmail { get; set; }
[Required]
public DateTime DatePosted { get; set; }
[Required]
public string CommentBody { get; set; }
public virtual Story Story { get; set; }
public virtual User User { get; set; }
}
and the ViewPostViewModel
public class ViewPostViewModel
{
public Story story { get; set; }
public Comment newComment { get; set; }
}
}
Thanks much for any help.
One answer would be to use #Html.EditorFor()
#Html.EditorFor(m => m.newComment)
Then on the folder where the view is placed you create a new folder called EditorTemplates with a view that is named exactly as the object type. In this case, Comment.cshtml
The view could be something like this ->
#model StoryWall.ViewModels.Comment
#Html.TextBoxFor(m => m.CommenterName)
#Html.TextBoxFor(m => m.CommenterEmail)
#Html.TextAreaFor(m => m.CommentBody)
This approach is the one I normally use to work with lists (useful in surveys or tests) but it also works with a single item.
Another approach could be to just add everything to the viewmodel since the viewmodel doesn't need to be a one to one mapping of the business objects or the database models. :)
Edit: Forgot to add. I think using this approach the method that receives the post will have to receive the whole ViewModel instead of just the comment. ->
public ActionResult AddComment(ViewPostViewModel vm)

Bind multiple values to a single checkbox and post it to controller

Model.cs
A campaign can have multiple images, that's why IEnumerable<int> ImageIdList.
public class Campaign
{
public int Id { get; set; }
public int CreatedBy { get; set; }
public int UpdatedBy { get; set; }
public IEnumerable<int> ImageIdList { get; set; }
}
View.cshtml
I want to download all the images related to a campaign, based on the ImageIdList, that's why I need to post all these ImageIds when a particular Campaign is checked and download button is clicked.
#model Campaign
#{
Layout = "....";
var assets = Model.AssetsInCampaign.ToList();
}
#using (Html.BeginForm("action-method", "controller", FormMethod.Post))
{
<div class="btnSubmit">
<input type="submit" value="Download Asset(s)" />
</div>
#foreach(var i in assets)
{
<div class="s_checkcol">
<input type="checkbox" name="ids" />
#foreach (var imageId in i.Where(c => c.AssetId == doc.FileDataId).SelectMany(c => c.ImageIdList))
{
<input type="hidden" name="ids" value=#(imageId)>
}
</div>
}
}
Controller.cs
public ActionResult DownloadFiles(IEnumerable<int> ids)
{
// code
}
NOTE: Only a part of code(where I'm facing the problem) is provided here. Its a DB first approach and in no way I can alter that (ORDERS).
I tried the above, but all of the ids are posted to the controller no matter how many checkboxes are selected.
Question: How should I bind the IEnumerable<int> ImageIdList property to a checkbox in View.cs and post the data to Controller.cs so that only the ids of selected checkboxes are posted?
This is a nice practice... it will work and Iam working with such a
manner (Iam sure that it will work very well) but one thing you have to be very carefull while coding this, little bit
complicated
Iam taking this effort not only for as an answer to this particular question.
Its for all stackoverflow users. Because i never found the below method anyware in stackoverflow.
I get this method by a long search. You people can use this.
It will help you to avoid for loops to bind the Checkboxlist
Its the best good for re-usability (need a single line (max: 20-25 chars to bind a CheckBoxList in Razor))
CheckBoxListItem.cs
create a New Class CheckBoxListItem //you can use any other names
public class CheckBoxListItem
{
public int ID { get; set; }
public string Display { get; set; }
public bool IsChecked { get; set; }
}
MyModel.cs
This is modelclass
public class MyModel
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
public IEnumerable<CheckBoxListItem> ChkList { get; set; }
}
HomeController.cs
This is controller
public ActionResult Index()
{
var model = new MyModel(){
Id = 0,
Name = "Your Name",
ChkList = dbContext.myTable.Select(x => new CheckBoxListItem { ID = x.MyTableFieldID, Display = x.MyTableFieldName, IsChecked = true })
//If you need only int part, then just avoid to bind data on Display field
};
return View(model);
}
[HttpPost]
public ActionResult Index(MyModel myModel) //Your model object on PostAction
{
IEnumerable<CheckBoxListItem> ChkList = myModel.ChkList;
// Here is your answer, You can see all your check box items (both checked and unchecked) in ChkList, it will shows all your checked items are true and non-checked items are false in IsChecked field
}
Here you have to give more patiance
Goto the Folder View>Shared>EditorTemplates and RightClick Add>View... and Create a new View with the same name CheckBoxListItem.cshtml
CheckBoxListItem.cshtml
#model Project.Models.CheckBoxListItem
<div class="">
#Html.HiddenFor(x => x.ID)
<div class="">
#Html.CheckBoxFor(x => x.IsChecked)
</div>
#Html.LabelFor(x => x.IsChecked, Model.Display, new { #class = "" })
</div>
Create your View
Index.cshtml
#model #model Project.Models.MyModel
<div class="form-group">
#Html.LabelFor(model => model.Id, htmlAttributes: new { #class = "" })
<div class="col-md-10">
#Html.EditorFor(model => model.Id, new { htmlAttributes = new { #class = "" } })
#Html.ValidationMessageFor(model => model.Id, "", new { #class = "" })
</div>
</div>
#Html.EditorFor(model => model.ChkList) //This only one line of code is enough to bind a checkBoxList in future
<input type="submit" value="Create" class="" />
You will get all these in your post action

Select 1 radiobutton out of three

I haven't worked with radiobuttons previously in MVC project.
And now when since i'm working with it for first time, i seem to face a problem.
What I want to do is, there will be questions, and user can select one answer from the available answers. Each answer will have a radiobutton.
Here is my model
public class CreateAppointmentSelectOptions
{
public Guid AppointmentId { get; set; }
//question1
[Display(Name = "Repeat invitation untill a common date is found")]
public bool RepeatTillCommonDateIsFound { get; set; }
[Display(Name = "Repeat times")]
[Range(1,5,ErrorMessage="Repeat times must be between {1} and {2}")]
public int RepeatTimes { get; set; }
//question 1
[Display(Name="Repeat invitation once")]
public Boolean RepeatOnce { get; set; }
//question 1
[Display(Name="Do not repeat invitation")]
public Boolean NoRepeat { get; set; }
//question 2
[Display(Name = "Cancel the invitation")]
public Boolean CancelInvitation { get; set; }
//question 2
[Display(Name="Plan appointment with my first available date")]
public Boolean FirstAvailableCommon { get; set; }
//question 2
[Display(Name="Plan with all participants available on the first available common date")]
public Boolean OwnerFirstAvailableCommon { get; set; }
}
and the controller
[HttpGet]
public ActionResult Create_WhatIf(Guid appointmentId)
{
var appointmentCondition = new CreateAppointmentSelectOptions
{
AppointmentId = appointmentId,
RepeatOnce = true,
NoRepeat = false,
RepeatTillCommonDateIsFound=false,
CancelInvitation = false,
OwnerFirstAvailableCommon=false,
FirstAvailableCommon = true
};
return View(appointmentCondition);
}
[HttpPost]
public ActionResult Create_WhatIf(CreateAppointmentSelectOptions options)
{
return View();
}
and the view
#model CreateAppointmentSelectOptions
#{
ViewBag.Title = "Create_WhatIf";
Layout = "~/Views/Shared/_Layout.cshtml";
}
<h2>What If?</h2>
#using (Html.BeginForm("Create_WhatIf", "Appointment", FormMethod.Post))
{
#Html.HiddenFor(m=>m.AppointmentId)
<div class="col-md-10">
<h3>What would you like to do if a common appointment with all participants cannot be made after the first invitation?</h3>
<div class='well'>
<div class="form-group">
<div class="input-group">
#Html.RadioButtonFor(m => m.RepeatTillCommonDateIsFound,Model.RepeatTillCommonDateIsFound)
#Html.LabelFor(m => m.RepeatTillCommonDateIsFound)
</div>
<div id="RepeatTimes">
#Html.EditorFor(m=>m.RepeatTimes)
</div>
<div class="input-group">
#Html.RadioButtonFor(m => m.RepeatOnce,Model.RepeatOnce)
#Html.LabelFor(m => m.RepeatOnce)
</div>
<div class="input-group">
#Html.RadioButtonFor(m => m.NoRepeat,Model.NoRepeat)
#Html.LabelFor(m => m.NoRepeat)
</div>
</div>
</div>
</div>
<div class="col-md-10">
<h3>What would you like to do if a common appointment cannot be made in the end?</h3>
<div class='well'>
<div class="form-group">
<div class="input-group">
#Html.RadioButtonFor(m => m.CancelInvitation,Model.CancelInvitation)
#Html.LabelFor(m => m.CancelInvitation)
</div>
<div class="input-group">
#Html.RadioButtonFor(m => m.OwnerFirstAvailableCommon,Model.OwnerFirstAvailableCommon)
#Html.LabelFor(m => m.OwnerFirstAvailableCommon)
</div>
<div class="input-group">
#Html.RadioButtonFor(m => m.FirstAvailableCommon,Model.FirstAvailableCommon)
#Html.LabelFor(m => m.FirstAvailableCommon)
</div>
</div>
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input class="btn btn-default" value="<<Previous" />
<input type="submit" class="btn btn-default" value="Next>>" />
</div>
</div>
}
#Scripts.Render("~/bundles/jquery")
#Scripts.Render("~/bundles/bootstrap")
#Scripts.Render("~/bundles/jqueryval")
This is view being rendered in browser currently
Question
(Well the editor below the question is something you can ignore.)
Why are all the radio button being selected by default? And what should I do in order to make sure that only 1 radio button can be selected for each question?
Each group of radio buttons should be bound to the same property on the model. Currently, each radio is its own property and therefore in its own group. And they are all checked because the 2 arguments passed to it are the same so a comparison always returns true (for example, m => m.RepeatOnce and Model.RepeatOnce are equal therefore radio is checked).
Instead, you need to add 1 property on your view model the represent each group. For example, question 2...
#Html.RadioButtonFor(m => m.Question2Answer, "CancelInvitation")
#Html.RadioButtonFor(m => m.Question2Answer, "OwnerFirstAvailableCommon")
#Html.RadioButtonFor(m => m.Question2Answer, "FirstAvailableCommon")
The second value is the value to be assigned to Question2Answer if the corresponding value is selected. (Here I am using a string, but you can also use an Enum)
When the form is submitted, you have to use the value in Question2Answer to populate the CancelInvitation, OwnerFirstAvailableCommon, and FirstAvailableCommon properties on the model. Likewise, if you display an existing entity, you have to populate the Question2Answer property on your view model before rendering the view.
--- Update ---
View Model would look something like this. In your controller, you need to populate it from the model...
public class CreateAppointmentSelectOptionsViewModel
{
public Guid AppointmentId { get; set; }
[Display(Name = "Repeat times")]
[Range(1,5,ErrorMessage="Repeat times must be between {1} and {2}")]
public int RepeatTimes { get; set; }
public string Question1Answer { get; set; } // should have a more meaningful name
public string Question2Answer { get; set; }
}
I think you forgot to set the groupName for your radio buttons.
http://msdn.microsoft.com/en-us/library/system.web.ui.webcontrols.radiobutton.groupname(v=vs.110).aspx
Try this
#Html.RadioButtonFor(m => m.CancelInvitation,Model.CancelInvitation,new { Name = "grp" })
#Html.RadioButtonFor(m => m.OwnerFirstAvailableCommon,Model.OwnerFirstAvailableCommon,new { Name = "grp" })
#Html.RadioButtonFor(m => m.FirstAvailableCommon,Model.FirstAvailableCommon,new { Name = "grp" }) &nbsp

Drop-Down Menu Causing Invalid Model State. ASP.NET MVC 3

I have a registration view in my web app. However, account registration would be unsuccessful and my drop-down menu would be outlined in red.
Here is my drop down code:
<div class="editor-label">
#Html.LabelFor(model => model.CompanyId, "Company")
</div>
<div class="editor-field">
#Html.DropDownList("Companies", "<Select Company>")
</div>
Here is my associated code in the view model:
[Display(Name = "Company")]
public int? CompanyId { get; set; }
public IEnumerable<SelectListItem> Companies { get; set; }
Here is my associated code in the controller before I return the view:
ViewBag.Companies = new SelectList(db.Companies, "CompanyId", "CompanyName");
As I debug, I find that ModelState.IsValid returns false, and therefore re-returns the view (and re-sets ViewBag.Companies in my case).
How do I complete a successful registration? I do fill out each required field within parameters. It is not required to select company. When I do not select a company, ModelState.IsValid returns true and runs through the code. However, I would like the user to have the option to associate with his company.
Thanks.
Why would you need setting ViewBag.Companies, when you've got the better approach - having select list into the model? No need to use ugly ViewBag
You should have something like this
ViewModel
public class RegisterViewModel
{
[Display(Name = "Company")]
public int? CompanyId { get; set; }
public IEnumerable<SelectListItem> Companies { get; set; }
}
Controller
[HttpGet]
public ActionResult Register()
{
RegisterViewModel viewModel = new RegisterViewModel();
viewModel.Companies = new SelectList(db.Companies, "CompanyId", "CompanyName");
return View(viewModel);
}
And in the view
<div class="editor-label">
#Html.LabelFor(model => model.CompanyId, "Company")
</div>
<div class="editor-field">
#Html.DropDownListFor(model => model.CompanyId, Model.Companies, "<Select Company>")
</div>
This will bind your CompanyId on server correctly.
Your model state will always be invalid for your dropdowns if your selected variable type is not a string. Change CompanyId to string. You can still use IEnumerable Companies {get;set;}
DropDownList is kind of weird in ASP.Net MVC3. It will not bind properly to an enumerable of SelectListItem. You need to store the bound, selected choice to a primitive in the model and the list of all options in a separate field.
<div class="editor-label">
#Html.LabelFor(model => model.CompanyId, "Company")
</div>
<div class="editor-field">
#Html.DropDownListFor(model => model.CompanyId, new SelectList(Model.Companies))
</div>
Then, in your model, change the IEnumerable of SelectListItems to an IEnumerable of strings.
[Display(Name = "Company")]
public string CompanyId { get; set; }
public IEnumerable<string> Companies { get; set; }
Finally, in your controller,
ViewBag.Companies = db.Companies;
Just as a sidenote, I suggest making your IEnumerable an IList instead. A dropdown menu definitely cares about the order in which the data is presented, so your model should reflect that.

Categories