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

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.

Related

DropDownListFor with ViewBag does not pass values

I want to pass a new booking via a create view where the user can select different locations. I can select the locations in the view, but the location attribute is still set to null, when I click submit, everything else works.
The locations are stored in a List in an infrastructure model, I am guessing, the problem is, that my values are LocationIds and not Location objects, but I don't know how to create an selected list with object values, since all the examples are with ids/names.
These are my models:
public class Infrastructure
{
public int InfrastructureId { get; set; }
[Required]
public List<Location> Locations { get; set; }
}
public class Location
{
public int LocationId { get; set; }
public Address Address { get; set; }
public int CountEmployee { get; set; }
public GPS Coordinates { get; set; }
public List<ChargingStation> ChargingStations { get; set; }
}
this is the Create in the Controller:
public IActionResult Create()
{
Infrastructure infrastructure = _infrastructure.GetRealInfrastructure();
List<Location> locations = infrastructure.Locations;
ViewBag.Locations = new SelectList(locations, "LocationId", "Address.City");
return View();
}
and this is the view:
<div class="form-group">
<label asp-for="end" class="control-label"></label>
<input asp-for="end" class="form-control" id="endTime"/>
<span asp-validation-for="end" class="text-danger"></span>
</div>
</div>
<div class="col-3">
<div class="form-group">
<label asp-for="location" class="control-label"></label>
#Html.DropDownListFor(model => model.location, (IEnumerable<SelectListItem>)ViewBag.Locations, "Choose Location", new { #class = "form-control" })
<span asp-validation-for="location" class="text-danger"></span>
</div>
</div>
The values for endtime for example are saved, but location is still set too null, so I am guessing I am missing some select/submit tag? Or as written before its because I pass an string value (LocationId) instead of an actual location object?

Failing to implement voting system with Asp.net MVC 5 controller

I'm trying to basically have users "like" a post, which I call "voting" for a post in my application.
I have a vote class which records the userid, the postid (called a story), and the rest is boilerplate.
public class Vote
{
public int Id { get; set; }
public string VoterId { get; set; }
public virtual ApplicationUser Voter { get; set; }
public int StoryId { get; set; }
public Story Story { get; set; }
public DateTime CreatedAt { get; set; }
}
In the story model, I have a reference to a collection of votes which I theoretically will call a .Count() and print the number of "votes" to the view when it comes time to render the post details page.
public virtual ICollection<Vote> Votes { get; set; }
Inside the razor view, when a user is looking at a post(story), deciding whether or not to vote for it, I have this form. So if the user logged in isn't the author of the story, then they can vote for the story.
#if (!Model.IsStoryOwner)
{
<div class="row mt-1">
<div class="col-3">
#using (Html.BeginForm("New", "Vote"))
{
#Html.AntiForgeryToken()
#Html.HiddenFor(x => x.Story.Id, new { StoryId = Model.Story.Id })
<div class="btn-group">
<button type="submit" class="btn btn-info voteBtn" id="LikeBtn">
<i class="fa fa-heart mr-2"></i>
Vote
</button>
</div>
}
</div>
</div>
}
This hits the following controller:
//POST /vote/new
//FOR adding a vote to a story
[Authorize]
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult New(Vote vote)
{
var voterId = User.Identity.GetUserId();
var newVote = new Vote
{
VoterId = voterId,
StoryId = vote.StoryId,
CreatedAt = DateTime.Now
};
dbContext.Votes.Add(newVote);
dbContext.SaveChanges();
return View();
}
however the storyId is coming through as null even though I've verified that it's not on the client-side when I run the application.
Because your current view code will generate the HTML markup for a hidden input element with name attribute value Story.Id.
<input name="Story.Id" type="hidden" value="1">
When the form is submitted, model binder will not be able to map the value of that input to StoryId property of Vote object. For model binder to properly map the values, the input element name should match with the property name of the class you are using as the parameter of your action method.
You can create a hidden input with the name attribute value matching to your property name
#using (Html.BeginForm("New", "Vote"))
{
#Html.AntiForgeryToken()
<input type="hidden" name="StoryId" value="#Model.Story.Id" />
<div class="btn-group">
<button type="submit" class="btn btn-info voteBtn" id="LikeBtn">
<i class="fa fa-heart mr-2"></i>Vote
</button>
</div>
}
Or you can use the Html.Hidden helper method which generates the same markup as above.
#Html.Hidden("StoryId",Model.Story.Id)
You are using HiddenFor the Story.Id, not StoryId.
If your Model contains a value for StoryId, you can simply use
#Html.HiddenFor(x => x.StoryId)

Start of with empty View Model MVC

I would like to create a view which is linked to multiple tables. From what I understand I need to create a View Model and link that to the page.
I get a couple of errors using the below
'PaymentViewModel' is a type, which is not valid in the given context.
An expression tree may not contain a dynamic operation (related to first error?)
I am new to MVC - come from ASP....Any help is appreciated
public class PaymentViewModel
{
public string playername { get; set; }
public DateTime dob { get; set; }
public string phone { get; set; }
public string email { get; set; }
public string clubname { get; set; }
public string productname { get; set; }
public decimal amount { get; set; }
public int transactionID { get; set; }
public bool approved { get; set; }
public string subtype { get; set; }
public DateTime subdate { get; set; }
}
Controller
I need to start with a blank view as this is the first step to register a player so the information is not in the database.
Below is the code I use to get a populated View.
public ActionResult Payment()
{
DateTime blank = Convert.ToDateTime("01-01-1900");
var prod = from p in db.Product
join c in db.Club on p.clubname equals c.clubname
where p.clubname == "Club1"
select new PaymentViewModel
{
productname = p.prodname,
clubname = c.clubname,
playername = c.add1,
dob = blank,
phone = c.phone,
email = c.email,
transactionID = 0,
amount = p.amount,
approved = Convert.ToBoolean("1"),
subtype = c.city,
subdate = blank
};
return View(prod);
}
View
#S4C.BAL.PaymentViewModel;
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>Player Name</h4>
<hr />
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<div class="form-group">
<b class="control-label col-md-2" style="">Full Name</b>
<div class="col-md-10">
#Html.EditorFor(model => model.playername, new { htmlAttributes = new { autofocus = "autofocus", #maxlength = "25", #class = "form-control" } })
#Html.ValidationMessageFor(model => model.playername, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
<br /><br />
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Create" class="btn btn-default" />
</div>
</div>
</div>
</div>
}
<div>#Html.ActionLink("Back to List", "Index")</div>
#section Scripts {#Scripts.Render("~/bundles/jqueryval")}
You cannot do approved = Convert.ToBoolean("1") in your select because the whole projection will happen at the database side and it does not know what Convert.ToBoolean() is. You need to do this in your view model:
public class PaymentViewModel {
// other properties ...
public string approved { get; set; }
public bool IsApproved {get {return this.approved == "1" }}
}
Also change the first line in your view to this:
#model S4C.BAL.PaymentViewModel
Not sure if I am understanding this correctly so please tell me if I'm wrong here.
Sounds like you know how to get a view filled with data from your database and you want to get an empty view without the data filled. To get an empty view request with just return a view without the model.
// Must request with /{Controller}/PaymentEmpty
Public ActionResult PaymentEmpty()
{
return View("Payment", new PaymentViewModel());
}
If you look at the default templates for ASP MVC applications the controller contains actions for Index, Details, Create, Edit and Delete. Thinking of actions in this manner can help with structuring your requests. Maybe place Payment into its own controller named PaymentsController and having the actions from the controller follow the default template.

Razor checkboxes for list of models

User Model
public class UserModel
{
public int UserId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Location { get; set; }
public IEnumerable<UserPets> UserPets { get; set; }
}
User Pets Model
public class UserPetsModel
{
public PetModel Pet{ get; set; }
public bool UserHasPet { get; set; }
}
Using these 2 models I am creating an edit page where a User can come in and edit which Pets they have.
To enable them to state which pets they have I am trying to use checkboxes.
Edit Page
#model Models.UserModel
#using (Html.BeginForm())
{
<div class="form-group">
#Html.LabelFor(model => model.FirstName)
#Model.FirstName
</div>
#foreach (var userPets in Model.UserPets)
{
#Model.Pet.AnimalName
<div>
#Html.CheckBoxFor(u => userPets .UserHasPet)
</div>
}
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Save" class="btn btn-default" />
</div>
</div>
}
The problem I am having is when trying to map the UserModel back to the controller action. When I press the save button, everything on the UserModel is being mapped back to the controller apart from the UserPetsModels which I believe is due to the use of the foreach.
Is there another way in which I can display a checkbox for each UserPetModel without using a foreach or a for loop.
Yes there is. You should create EditorTemplate for your UserPetsModel. It will look like:
#model UserPetsModel
#Model.Pet.AnimalName
<div>
#Html.CheckBoxFor(model => model.UserHasPet)
</div>
And then you can simply do:
#Html.EditorFor(model => model.UserPets)
EditorFor will create right binding for you. Note that you should create EditorTemplate only for UserPets and it also will work for List<UserPetsModel> and IEnumarable<UserPetsModel> with the same syntax that i show.
I would suggest replace the loop with EditorTemplate. So your
#foreach (var userPets in Model.UserPets)
{
#Model.Pet.AnimalName
<div>
#Html.CheckBoxFor(u => userPets.UserHasPet)
</div>
}
would look like:
<div class="row">
#Html.EditorFor(m => m.UserPets)
</div>
And define a view in (~/Views/Shared/EditorTemplates/UserPets.cshtml) like:
#model UserPetsModel
#Html.HiddenFor(x => x.Pet.PetId)
#Html.LabelFor(x => x.UserHasPet, Model.Pet.AnimalName)
#Html.CheckBoxFor(x => x.UserHasPet)

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

Categories