Data annotation trigger validation on Get method - asp.net-mvc

I have this viewmodel
public class ProductViewModel : BaseViewModel
{
public ProductViewModel()
{
Categories = new List<Categorie>
}
[Required(ErrorMessage = "*")]
public int Code{ get; set; }
[Required(ErrorMessage = "*")]
public string Description{ get; set; }
[Required(ErrorMessage = "*")]
public int CategorieId { get; set; }
public List<Categorie> Categories
}
My controller like this
[HttpGet]
public ActionResult Create(ProductViewModel model)
{
model.Categories = //method to populate the list
return View(model);
}
The problem is, as soon as the view is exhibited, the validation is fired.
Why this is happening?
Thanks in advance for any help.
Update
The view is like this
#using (Html.BeginForm("Create", "Product", FormMethod.Post, new { #class = "form-horizontal", #role = "form" }))
{
<div class="form-group">
<label for="Code" class="col-sm-2 control-label">Code*</label>
<div class="col-sm-2">
#Html.TextBoxFor(x => x.Code, new { #class = "form-control"})
</div>
</div>
<div class="form-group">
<label for="Description" class="col-sm-2 control-label">Desc*</label>
<div class="col-sm-2">
#Html.TextBoxFor(x => x.Description, new { #class = "form-control", maxlength = "50" })
</div>
</div>
<div class="form-group">
<label class="col-sm-2 control-label">Categorie*</label>
<div class="col-sm-4">
#Html.DropDownListFor(x => x.CategorieId, Model.Categories, "Choose...", new { #class = "form-control" })
</div>
</div>

You GET method has a parameter for your model, which means that the DefaultModelBinder initializes and instance of the model and sets its properties based on the route values. Since your not passing any values, all the property values are null because they all have the [Required] attribute, validation fails and ModelState errors are added which is why the errors are displayed when the view is first rendered.
You should not use a model as the parameter in a GET method. Apart from the ugly query string it creates, binding will fail for all properties which are complex objects and collections (look at you query string - it includes &Categories=System.Collections.Generic.List<Categorie> which of course fails and property Categories will be the default empty collection). In addition, you could easily exceed the query string limit and throw an exception.
If you need to pass values to the GET method, for example a value for Code, then you method should be
[HttpGet]
public ActionResult Create(int code)
{
ProductViewModel model = new ProductViewModel
{
Code = code,
Categories = //method to populate the list
};
return View(model);
}

Related

MVC BeginCollectionItem helper not binding to model instance within view model

Building a dynamic list in MVC5. Form will post to the controller and I can see the StudentList correctly populated in chrome dev tools but in the controller, the StudentList is empty.
Model:
public class Student
{
public int id { get; set; }
public string Name { get; set; }
}
public class StudentViewModel
{
public Student student { get; set; }
public List<Student> StudentList { get; set; }
}
View:
#model TaskTrack.ViewModels.StudentViewModel
#using (Html.BeginForm("Create", "Timesheet", FormMethod.Post, new { #id = "addTaskForm" }))
{
<div id="task-row">
//Partial view renders here.
</div>
<button class="btn save-day" type="submit" >Save Day</button>
}
Partial view:
#model TaskTracker.ViewModels.StudentViewModel
#using (Html.BeginCollectionItem("StudentList"))
{
<div class="form-row">
<div class="form-group col-2">
#Html.EditorFor(model => model.Student.Name, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.Student.Name, "", new { #class = "text-danger" })
</div>
</div>
}
If I take out the Student instance from the ViewModel and replace it with the student model properties, then change the partial view to bind directly to the Name property, it will work.
#Html.EditorFor(model => model.Student.Name, new { htmlAttributes = new { #class = "form-control" } })
to
#Html.EditorFor(model => model.Name, new { htmlAttributes = new { #class = "form-control" } })
Controller post:
[HttpPost]
public ActionResult Create(StudentViewModel model)
When I intercept the post before it hits the controller, I can see it has bound correctly, but in the controller StudentList is empty.
I am trying to avoid having the student model duplicated in the view model because it's duplicate and all the validation rules are already in the Student model, so I would have to duplicate all that in the viewmodel as well, which seems wrong.

How to pass different list of data from view to controller MVC

currently I facing a very tricky problem of passing different list of data from view to controller.
I have created two input box to submit my data to controller so that it can be saved into CreateAccountsDB and further display it in the list of
Selected Subcon when Create button is pressed.
The problem I face here is:
when pressing the Create button with entered data from NewCompanyName textbox and NewEmail textbox, those entered data do pass data from View to Controller and save data into CreateAccountDB (not showing in View), but the entered data is not displaying in the list of Selected Subcon.
Create View
Here is the model.
public class Tender
{
public int ID { get; set; }
public string CompanyName { get; set; }
public List<CreateAccount> FrequentCompanyName { get; set; }
public List<CreateAccount> SuggestCompanyName { get; set; }
public List<CreateAccount> SelectedCompanyName { get; set; }
public string CompanyNameNew { get; set; }
public string EmailNew { get; set; }
public int? TradeID { get; set; }
public virtual Trade Trade { get; set; }
public int? CreateAccountID { get; set; }
public virtual CreateAccount CreateAccount { get; set; }
}
Here is the Get Method of Create function in controller:
[httpGet]
public ActionResult Create(int? id)
{
Tender tender = new Tender();
tender.FrequentCompanyName = db.createaccountDB.Include(tm => tm.Trade).Where(td => td.Frequency == 32).ToList();
tender.SuggestCompanyName = db.createaccountDB.Include(tm => tm.Trade).ToList();
if (tender.SelectedCompanyName == null)
{
tender.SelectedCompanyName = new List<CreateAccount>().ToList();
}
return View(tender);
}
and Here is my Post Method of Create function:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include = "ID,CompanyName,TradeID,FrequentCompanyName,SelectedCompanyName,CreateAccountID")] Tender tender ,string CompanyNameNew, string Emailnew)
{
CreateAccount accnew = new CreateAccount();
accnew.CompanyName = CompanyNameNew;
accnew.Email = Emailnew;
if(ModelState.IsValid)
{
db.createaccountDB.Add(accnew);
db.SaveChanges();
}
if (tender.SelectedCompanyName == null)
{
tender.SelectedCompanyName = new List<CreateAccount>().ToList();
}
tender.FrequentCompanyName = db.createaccountDB.Include(tm => tm.Trade).Where(td => td.Frequency == 32).ToList();
tender.SuggestCompanyName = db.createaccountDB.Include(tm => tm.Trade).ToList();
tender.SelectedCompanyName.ToList().Add(accnew);
return View(tender);
}
and Here is my Create View:
#model Tandelion0.Models.Tender
#{
ViewBag.Title = "Create";
}
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-group">
#*#Html.LabelFor(model => model.ProjectName, htmlAttributes: new { #class = "control-label col-md-3" })*#
<div class="col-md-3">
<h5>New Company Name</h5>
#Html.EditorFor(model => model.CompanyNameNew, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.CompanyNameNew, "", new { #class = "text-danger" })
</div>
<div class="col-md-3">
<h5>New Email</h5>
#Html.EditorFor(model => model.EmailNew, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.EmailNew, "", new { #class = "text-danger" })
</div>
<div class="container" align="center">
<input type="submit" value="Create" class="btn btn-default" />
</div>
</div>
<div class="container row">
<!--selected subcon column-->
<div class="container row col-sm-4">
<h4>
Selected Subcon
</h4>
<div style="overflow-y: scroll; height:250px;">
<table class="table table-hover">
#foreach (var item in Model.SelectedCompanyName)
{
<tbody>
<tr>
<td>
#Html.DisplayFor(modelItem => item.CompanyName)
</td>
</tr>
</tbody>
}
</table>
</div>
</div>
</div>
}
So far I manage to save data from view into CreateAccountsDB when create button is pressed, but those data just couldn't pass it from Post method Create function to Get method Create function in Controller. The data and the list become null immediate after come out from post method Create function.
Because of data becomes null, the view couldn't receive any data from controller.
May I know how can i solve the the problem of passing data from controller to view? Is the way I pass data totally wrong?
Any advice is truly appreciated.
In your HttpPost Action method :
Instead of :
tender.SelectedCompanyName.ToList().Add(accnew);
You should be doing:
tender.SelectedCompanyName.Add(accnew);
Calling ToList().Add(object) won't actually add to SelectedCompanyName.Instead it will add to the new list object created by calling ToList() method which you are not assigning back to tender.SelectedCompanyName.
A better approach however would be to use Post/Redirect/Get Pattern.
Instead of returning a view from your post method , do a temorary redirect to your [HttpGet]Create action method passing the id of the tender.

Model binding doesn't work for complex object

Here's the view I'm going to post:
#model WelcomeViewModel
#using (Html.BeginForm("SignUp", "Member", new { ReturnUrl = ViewBag.ReturnUrl }, FormMethod.Post))
{
....
<div class="form-group">
#Html.EditorFor(model => model.SignUp.CompanyName, new {htmlAttributes = new {#class = "form-control" }})
</div>
<div class="form-group">
#Html.EditorFor(model => model.SignUp.RegisteredNo, new {htmlAttributes = new {#class = "form-control" } })
</div>
....
<button type="submit" name="signup" class="btn">Register</button>
}
ViewModel:
public class WelcomeViewModel
{
public SignInViewModel LogOn { get; set; }
public SignUpViewModel SignUp { get; set; }
}
Action method:
[HttpPost, AllowAnonymous, ValidateAntiForgeryToken]
public virtual async Task<ActionResult> SignUp(SignUpViewModel model)
{
if (!ModelState.IsValid)
return View("SignIn", new WelcomeViewModel { SignUp = model });
// other code
return View();
}
When I post the data, the model gets null. I know the inputs will be generated like:
<input id="SignUp_CompanyName" name="SignUp.CompanyName">
But the model binder accepts this:
<input id="SignUp_CompanyName" name="CompanyName">
Now I want to know how can I remove that prefix? I know I can explicitly add name for each input:
#Html.TextBoxFor(model => model.SignUp.CompanyName, new { Name = "CompanyName" })
but I want to do it in a strongly type way.
Perhaps the easiest way would be to apply the [Bind] attribute with its Prefix set to "SignUp":
public async Task<ActionResult> SignUp([Bind(Prefix="SignUp")] SignUpViewModel model)
See MSDN

how can i assign value to textbox in MVC - Sharepoint Provider-hosted

I want to get and assign value to my textbox from controller.
here is my textbox:
<input type="text" class="form-control" id="RegardingTo" name="RegardingTo" value="??????"/>
then i want to get the value from this action.
public ActionResult Edit(int? RequestID)
{
if (RequestID <= 0)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
var ReqID = db.usp_RequestGetDetails(RequestID);
if (ReqID == null)
{
return HttpNotFound();
}
return View();
}
please help :)
Yes you can assign value to the textbox using Model, First of all create a model then link that model with your view. And In Controller assign the value to model and return to the view.
Or At runtime if you want to assign the value to your text box then you can use Ajax call to your controller and get the value.
Please revert in case of any query.
See what i am doing to do the same, Make a custom model with your relevant fields, then assign values to them in controller, and pass this values to View, And that's it. :)
Custom Model
public partial class QuoteParameter
{
public Nullable<System.DateTime> TripStartDateLimit { get; set; }
public Nullable<System.DateTime> TripEndDateLimit { get; set; }
public int PolicyId { get; set; }
}
Controller
public ActionResult Index()
{
QuoteParameter quote = new QuoteParameter();
quote.TripEndDateLimit = DateTime.Now;
quote.TripEndDateLimit = DateTime.Now;
quote.PolicyId = 5;
return View(quote);
}
View
#model EHIC.Models.Models.QuoteParameter
By Razor syntax
<div class="row-fluid span12">
<div class="span4">
<p><strong>Trip Start Date Limit :</strong></p>
</div>
<div class="span5">
#Html.TextBoxFor(model => model.TripStartDateLimit, "{0:dd/MM/yyyy}", new { #class = "form-control", #placeholder = "Policy StartDate Limit", #required = true })
</div>
</div>
<div class="row-fluid span12">
<div class="span4">
<p><strong>Trip End Date Limit :</strong></p>
</div>
<div class="span5">
#Html.TextBoxFor(model => model.TripEndDateLimit, "{0:dd/MM/yyyy}", new { #class = "form-control", #placeholder = "Policy EndDate Limit", #required = true })
</div>
</div>
By HTML Code
<input type="text" class="form-control" id="TripStartDateLimit" name="TripStartDateLimit" value="#Model.TripStartDateLimit"/>
<input type="text" class="form-control" id="TripEndDateLimit" name="TripEndDateLimit" value="#Model.TripEndDateLimit"/>
EDIT
By Click on this button you can send the PolicyId(as an example) to controller, and then you can do whatever you want there..!!!
<a href='../../controller/Edit?PolicyId=#Models.PolicyId'>
<span title='Edit'></span>
</a>
#Html.ActionLink("Edit","Edit", new { id = item.RequestID })
You can find the PolicyId which you sent from the Edit Page..
public ActionResult Edit(int id)
{
//Get your data from Store_procedure..
return View();
}

The ViewData item that has the key 'ShelfId' is of type 'System.Int32' but must be of type 'IEnumerable<SelectListItem>'

Problem
I use the following code very similarily somewhere else in my application, but it is not working. I am completely stumped.
The ViewData item that has the key 'ShelfId' is of type 'System.Int32' but must be of type 'IEnumerable<SelectListItem>'
This is thrown during the post method. My model state is invalid.
Code
Models
Shelf
public class Shelf
{
[Key]
public int ShelfId
[Display(Name = "Shelf Id")]
[Required]
public string ShelfName
public virtual List<Book> Books {get; set;}
}
Book
public class Book
{
public int BookId
[Required]
[StrengthLength(160, MinimumLength = 8)]
public string BookName
public int ShelfId
public Shelf shelf {get; set;}
}
Controller
// GET: Units/Create
public async Task<IActionResult> Create()
{
var shelves = await _db.Shelves.OrderBy(q => q.Name).ToListAsync();
ViewBag.SelectedShelves = new SelectList(shelves, "ShelfId", "Name");
return View();
}
// POST: Units/Create
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create(Book book)
{
book.CreatedBy = User.Identity.GetUserName();
book.Created = DateTime.UtcNow;
book.UpdatedBy = User.Identity.GetUserName();
book.Updated = DateTime.UtcNow;
if (ModelState.IsValid)
{
db.Units.Add(unit);
await db.SaveChangesAsync();
return RedirectToAction("Index");
}
return View(book);
}
view
#model AgentInventory.Models.Book
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>Create Unit</title>
</head>
<body>
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal well bs-component" style="margin-top:20px">
<h4>Unit</h4>
<hr />
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<div class="form-group">
<div class="control-label col-md-2">Room</div>
<div class="col-md-10">
#Html.DropDownListFor(model => model.ShelfId, (SelectList)ViewBag.SelectedShelves, "All", new { #class = "form-control" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.Name, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.BookName, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.BookName, "", new { #class = "text-danger" }
</div>
</div>
<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>
#Html.ActionLink("Back to List", "Index")
</div>
Attempts
I tried:
Adding #Html.HiddenFor(model=>model.ShelfId) in the create view, but that didn't work.
I have looked at similar issues on stackoverflow, but none of the fixes worked for me. (IE - hiddenfor, different kinds of selectlists)
Since I am new to MVC framework, I would be grateful for any assistance. I don't understand why this code works for two other kinds of models (Building and room), but not my current two models? It's weird.
PS - Is there a way to do this easily without using viewbag as well?
The reason for the error is that in the POST method when you return the view, the value of ViewBag.SelectedShelves is null because you have not set it (as you did in the get method. I recommend you refactor this in a private method that can be called from both the GET and POST methods
private void ConfigureViewModel(Book book)
{
var shelves = await _db.Shelves.OrderBy(q => q.Name).ToListAsync();
// Better to have a view model with a property for the SelectList
ViewBag.SelectedShelves = new SelectList(shelves, "ShelfId", "Name");
}
then in the controller
public async Task<IActionResult> Create()
{
// Always better to initialize a new object and pass to the view
Book model = new Book();
ConfigureViewModel(model)
return View(model);
}
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create(Book book)
{
if (!ModelState.IsValid)
{
ConfigureViewModel(book)
return View(book);
}
// No point setting these if the model is invalid
book.CreatedBy = User.Identity.GetUserName();
book.Created = DateTime.UtcNow;
book.UpdatedBy = User.Identity.GetUserName();
book.Updated = DateTime.UtcNow;
// Save and redirect
db.Units.Add(unit);
await db.SaveChangesAsync();
return RedirectToAction("Index");
}
Note your Book class contains only fields, not properties (no { get; set; }) so no properties will be set and the model will always be invalid because BookName has Required and StringLength attributes.
Also you have not shown all the properties in your model (for example you have CreatedBy, Created etc. and its likely that ModelState will also be invalid because you only generate controls for only a few properties. If any other properties contain validation attributes, then ModelState will be invalid. To handle this you need to create a view model containing only the properties you want to display edit.
public class BookVM
{
public int Id { get; set; }
[Required]
[StrengthLength(160, MinimumLength = 8)]
public string Name { get; set; }
public int SelectedShelf { get; set; }
public SelectList ShelfList { get; set; }
}
Then modify the private method to assign the SelectList to the view model (not ViewBag, and in the controller methods, pass a new instance of BookVM to the view, and post back to
public async Task<IActionResult> Create(BookVM model)
{
if (!ModelState.IsValid)
{
ConfigureViewModel(model)
return View(model);
}
// Initialize a new Book and set the properties from the view model
}

Resources