Adding validation with DataAnnotations - c#

I have a form for deleting an object from a table. I would like to put a textfield which would validate the input before pressing the Delete button.
The entity model of the objects looks like this (it has many more attributes, but I just left the important one):
public partial class card
{
public string reason { get; set; }
}
The controller method of the POST (delete) request looks like this:
// POST: /card/Delete/5
[HttpPost, ActionName("Delete")]
[ValidateAntiForgeryToken]
public ActionResult DeleteConfirmed(int id)
{
card temp_card = db.cardss.Find(id);
temp_card.deleted = true;
db.SaveChanges();
if (ModelState.IsValid)
return RedirectToAction("Index");
return View(temp_card);
}
I've read, I have to create another class and use MetaDataAnnotations for this to work, since I'm using entity models. So I wrote this:
[MetadataType(typeof(CardMetaData))]
public partial class card
{
public string reason { get; set; }
}
public class CardMetaData
{
[Required(ErrorMessage = "Write a reason for deletion.")]
public string reason { get; set; }
}
And in my Delete.aspx are the following lines:
<%= Html.ValidationSummary("Delete was unsuccessful.") %>
<div class="display-field">
<%: Html.TextBoxFor(model => model.reason) %>
<%: Html.ValidationMessageFor(model => model.reason) %>
</div>
This doesn't display a message if I press the delete button and the textfield is empty. What am I missing?

You'll need a view model with a [Required] attribute for the Reason property
public class DeleteConfirmViewModel
{
[Required]
public string Reason { get; set; }
}
Then rework your DeleteConfirmed action. You're current implementation is just not working because you first update the database and then validate if your input model is correct. Next is that you should not redirect in case of a non valid model because you will loose the ModelState containing the error (messages). A correct implementation of DeleteConfirmed will look like this:
[HttpPost, ActionName("Delete")]
[ValidateAntiForgeryToken]
public ActionResult DeleteConfirmed(int id, DeleteConfirmViewModel viewModel)
{
if (ModelState.IsValid)
return View(viewModel);
card temp_card = db.cardss.Find(id);
temp_card.deleted = true;
temp_card.reason = viewModel.Reason;
db.SaveChanges();
return View(temp_card);
}
In you're view you will need to show a validation message in case no reason was given
#Html.ValidationMessageFor(m => m.Reason)
So a working input field setup for the reason field in your view could look like this
#Html.LabelFor(m => m.Reason)
#Html.EditorFor(m => m.Reason)
#Html.ValidationMessageFor(m => m.Reason)
Edit
To render the DeleteConfirmed view you'll need to create a view model and pass it to the view
[HttpGet, ActionName("Delete")]
public ActionResult DeleteConfirmed(int id)
{
return View(new DeleteConfirmViewModel());
}

[HttpPost]
public ActionResult ControllerName(ModelClassName viewModel)
{
if (!ModelState.IsValid)
return View("ViewName", viewModel);//passes validation errors back to the view
//do w.e
}

Related

POST controller action method is having a NULL ViewModel parameter

My POST controller isn't able to capture the ViewModel parameter I set and I'm very confused as I have a different set of POST controller and it can capture the ViewModel parameter.
My code looks like this,
View Page
#model MyProject.Web.ViewModels.MyViewModel
#{
ViewBag.Title = "Home";
ViewBag.Description = "My Project";
ViewBag.SubDescription = "My Project Tool";
Layout = null;
}
#using (Html.BeginForm())
{
#Html.TextBoxFor(m => m.Filter)
<input type="submit" class="btn btn-primary btn-inline-right input-tab" value="Search" />
}
Controller
using MyProject.Web.ViewModels;
[HttpGet]
[Route("Home/Index")]
public async Task<ActionResult> Index()
{
...await API integration code here...
return View(MyViewModel);
}
[HttpPost]
[Route("Home/Index/{viewmodel}")]
public ActionResult Index(MyViewModel viewmodel) <-- all properties of viewmodel are NULL
{
return View();
}
View Model
using MyProject.Web.Models;
using System.Collections.Generic;
namespace MyProject.Web.ViewModels
{
public class MyViewModel
{
public User UserInfo;
public List<Client> Clients;
public string Filter;
}
}
I feel this is a very small mistake, maybe due to overlooking too much. Hopefully someone could take a look and help.
The problem is with the route you have defined on the top of your Post action [Route("Home/Index/{viewmodel}")]
You don't need that {viewmodel} in that URL as you are not posting anything in the query string, you are posting a complex object inside the body of your HTTP Post.
Remove that route and it should work.
also, ASP.NET mvc maps the inputs to the Model properties based upon the name attributes on them like <input name="abc"> will map this input to a property named abc on a ViewModel or just a parameter. In your case #Html.TextBoxFor(m => m.Filter) does that automatically.
Hope this helps.
use this I hope useful:
#using (Html.BeginForm("Index", "HomeController", FormMethod.Post))
Change from public string Filter to property public string Filter {get;set;}
and change route to [Route("Home/Index")] instead of [Route("Home/Index/{viewmodel}")].
I tested and it worked.
public class MyViewModel
{
public User UserInfo { get; set; }
public List<Client> Clients { get; set; }
public string Filter { get; set; }
}
[HttpPost]
[Route("Home/Index")]
public ActionResult Index(MyViewModel viewmodel)
{
return View();
}

Passing variables from controller to display in view MVC 4 Entity framework

I am trying to display a list of all users registered to my application. I used the register page to both display all users registered and create new users from there.
I'm passing user list to my Register view like this:
[Authorize(Roles="Admin")]
public ActionResult Register()
{
using (var ctx = new UsersContext())
{
return View(ctx.UserProfiles.ToList());
}
}
I'm unable to loop through ctx.UserProfiles.ToList() from
#foreach (var user in Model)
{
<tr>
<td>#user.UserId</td>
<td>#user.UserName</td>
</tr>
}
I have #model MvcApplication1.Models.RegisterModel on the top of my cshtml.
I'm new to this, how can I pass a userlist from my controller to view?
I refered to this question here, which did not become help full because I'm unable to add #using MvcApplication1.Models
#model IEnumerable<UserProfile> to my page without compilation/parse errors.
Alright, now we have a little more information, it sounds to me like you want to allow someone to register, whilst listing all UserProfiles. As a view can only accept one model, you need to aggregate RegisterModel and your list of profiles together, and pass that instead:
public class UserProfilesRegisterModel
{
public RegisterModel RegistrationInfo { get; set; }
public List<UserProfile> UserProfiles { get; set; }
}
Now your account controller is going to need changing:
[Authorize(Roles="Admin")]
public ActionResult Register()
{
using (var ctx = new UsersContext())
{
var model = new UserProfilesRegisterModel();
model.UserProfiles = ctx.UserProfiles.ToList();
return View(model);
}
}
Now your view will look like this:
#model MvcApplication1.Models.UserProfilesRegisterModel
// rest of view
#foreach (var user in Model.UserProfiles)
{
<tr>
<td>#user.UserId</td>
<td>#user.UserName</td>
</tr>
}
Note that you'll need to change the parts of your view which access properties on RegisterModel. So for example:
#Html.LabelFor(m => m.UserName)
Would now become:
#Html.LabelFor(m => m.RegistrationInfo.UserName)
Now that the rest is working, you'll need to make a few more changes in AccountControllers Register action. This:
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public ActionResult Register(RegisterModel model)
becomes:
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public ActionResult Register(UserProfilesRegisterModel model)
Finally, these:
WebSecurity.CreateUserAndAccount(model.UserName, model.Password);
WebSecurity.Login(model.UserName, model.Password);
become:
WebSecurity.CreateUserAndAccount(model.RegistrationInfo.UserName, model.RegistrationInfo.Password);
WebSecurity.Login(model.RegistrationInfo.UserName, model.RegistrationInfo.Password);
Use
#model System.Collections.Generic.IEnumerable<MvcApplication1.Models.UserProfile>
that should do the trick if I got your problem right and guessed the namespace.

MVC List Null on Postback

My Controller populates my Model with a list with strings that appear in a DropDownList in my View. When the view is posted back to my Controller, that list is suddenly null. Why is it null, and what happened to the list of strings I created?
The list was properly populated and shows up in the View. The remainder of the form elements DO properly post back. For example, selectedName has whatever name the user clicked on. The only thing that is not posting back is nameList.
Here is the relevant part of my model,
public class MyModel
{
[Display(Name = "Selected")]
public string selectedName{ get; set; }
[Display(Name = "Names")]
public List<string> nameList{ get; set; }
}
the relevant Get and Post parts of my Controller,
public class MyController: Controller
{
[HttpGet]
public ActionResult Index()
{
List<string> nameList= getNames();
MyModel model = new MyModel()
model.nameList= nameList;
// Now, model.nameList has a bunch of stuff in it
return View(model);
}
[HttpPost]
public ActionResult Index(MyModel model)
{
if(model.nameList== null)
{
cry();
postOnStackOverflow();
}
return View(model);
}
}
and the relevant part of my View (which is encapsulated inside of a form).
<p>
#Html.LabelFor(c => c.nameList):
#Html.DropDownListFor(c => c.selectedName, new SelectList(Model.nameList), new { onchange = "this.form.submit();" })
</p>
Only the value of the drop down list is posted when you post the form. I assume that your control in question is on a form.
I am not sure why you want to always return to the view you posted from, but you need to repopulate the list:
[HttpPost]
public ActionResult Index(MyModel model)
{
List<string> names = getNames();
model.nameList = names;
return View(model);
}
That is the expected behaviour considering what you have in your view. You need to reload the namelist collection property incase you are returning model to the same view again.
[HttpPost]
public ActionResult Index(MyModel model)
{
if(ModelState.IsValid)
{
// Save and redirect
}
//reload the collection again and return the model to the view
model.nameList=getNames();
return View(model);
}

TextBoxFor is not refreshing after postback

I'm probably making a stupid mistake somewhere. I would appreciate your help in the following.
I have sample MVC3 application with a single editable field that is displayed to user with TextBoxFor method. In the Index(POST) action I change the value but it still remains the same. What am I doing wrong?
My code:
Model:
public class TestModel
{
public string Name { get; set; }
}
View:
using (Html.BeginForm())
{
#Html.TextBoxFor(m => m.Name)
<input type="submit" />
}
Controller:
public ActionResult Index()
{
return View("Index", new TestModel() { Name = "Before post" });
}
[HttpPost]
public ActionResult Index(TestModel model)
{
model.Name = "After post";
return View("Index", model);
}
If I replace TextBoxFor with TextBox or DisplayTextFor then it works correctly.
I believe you must call ModelState.Clear() inside your [HttpPost] action, before you set the new value.
According to this answer, which has a very good explanation: How to update the textbox value #Html.TextBoxFor(m => m.MvcGridModel.Rows[j].Id)
See this too: ASP.NET MVC 3 Ajax.BeginForm and Html.TextBoxFor does not reflect changes done on the server
Although it seems you're not using Ajax.BeginForm, the behavior is the same.
Including an example as suggested by #Scheien:
[HttpPost]
public ActionResult Index(TestModel model)
{
ModelState.Clear();
model.Name = "After post";
return View("Index", model);
}

MVC models don't persist in a form

I declare a model with 4 string fields. 3 of which are read-only on the form:
public class HomeModel
{
[ReadOnly(true)]
[DisplayName("Service Version")]
public string ServiceVersion { get; set; }
[ReadOnly(true)]
[DisplayName("Session Id")]
public string SessionId { get; set; }
[ReadOnly(true)]
[DisplayName("Visiting from")]
public string Country { get; set; }
[DisplayName("Search")]
public string SearchString { get; set; }
}
I pass the model, after populating it, to my form:
[HttpGet]
public ActionResult Index()
{
var model = new HomeModel
{
Country = "Australia",
SearchString = "Enter a search",
ServiceVersion = "0.1",
SessionId = "76237623763726"
};
return View(model);
}
And the form is displayed as I expect:
<h2>Simple Lookup</h2>
#Html.LabelFor(m=>m.ServiceVersion): #Model.ServiceVersion<br/>
#Html.LabelFor(m=>m.SessionId): #Model.SessionId<br/>
#Html.LabelFor(m=>m.Country): #Model.Country<br/>
<p>
#using(Html.BeginForm())
{
#Html.LabelFor(m => m.SearchString)
#Html.TextBoxFor(m => m.SearchString)
<button type="submit" name="btnSearch">Search</button>
}
</p>
But, when I submit the form, and get the model back from the form, only the value of the SearchString is populated.
[HttpPost]
public ActionResult Index(HomeModel model)
{
return View(model);
}
Is it right that the other fields have been 'lost'? Does MVC not preserve the other members of the model class? And if this is expected - is there a way to re-get these? Or would I need to go back to my database, populate the model with the old values, and then use the new values from the form model?
It's possible the validity of wanting to read 'read-only' fields back from the model is questioned.. which is fair - but in the event that I find something suspect about the posted data, maybe I want to re-show the screen, and not have to re-read the data from a database again?
This is the correct behavior. Only the elements inside form will be posted to your action. Since it is posting the form so your fields should be inside the form in order to get them on your post method.
Update
Also, you cannot read particular field on your action method if you have taken that field readonly on your view. eg: displaying using #Html.LabelFor. In order to get field back on your action use #Html.HiddenFor if field is not to be edited.

Categories

Resources