Range validation for textbox not working in MVC3 - c#

I am trying to raise validation error in an ASP.Net MVC 3 application.
When the user enters a number that is greater than 1000 an error message should be displayed. with the following code on a view model it doesn't seem to be working.
What do i need to change?
[Range(0, 1000, ErrorMessage = "Total number of rows to display must be between 0 to 1000")]
public int DisplayTop { get; set; }
cshtml :
#model Calc.Models.CountingVM
#{
ViewBag.Title = "Reports";
Layout = "~/Views/Shared/_reportsLayout.cshtml";
}
#using (Html.BeginForm("Reports", "Calc", FormMethod.Post, new { #id = "frmReport" }))
{
.........
#Html.TextBoxFor(c => c.DisplayTop, "1000")
#Html.ValidationMessageFor(c => c.DisplayTop)
}
Action Method :
public ActionResult Reports(string ReportName, CalcCriteria criteria)
{
if ((criteria == null) || (criteria.nId == null))
{
criteria = TempData["criteria"] as CalcCriteria;
TempData["criteria"] = criteria; // For reload, without this reloading the page causes null criteria.
}
ReportType c = (ReportType)Enum.Parse(typeof(ReportType), ReportName, true);
JavaScriptSerializer serializer = new JavaScriptSerializer();
string vmJson = string.Empty;
switch (c)
{
.....
int displayTop;
..........
case ReportType.Inventory_Counts_Report:
..............
displayTop = Convert.ToInt32(Request["DisplayTop"]);
........
return View("Counting", CountingVM);
default:
return View();
}
return View(); }
Thanks
BB

You also need to display the validation message:
#Html.ValidationMessageFor(c => c.DisplayTop)

Try
#Html.EditorFor(c => c.DisplayTop, "1000")
I guess, that the problem occures, becase your property is of type int, and the range is for type int, byt you are showing it in a textbox.
Also you need to add ModelState.IsValid in your controller. For example:
[HttpPost]
public ActionResult Create(YourModel model)
{
if(ModelState.IsValid)
{
// Put your logic here
}
return View(create)
}

Related

return to view after addModelError

in this project i create cardGroup. in httpGet Method we get some needed info and pass to view to fill dropdown. when httpPost trigger if some field Date has Problem we must return error with addModelError but after return View, all ViewData Clear and Return Exception. how can handle this. just show error in view.
[HttpGet]
[Route("CreateCardGroup")]
public ActionResult CreateCardGroup()
{
var discounts =
UnitOfWork.DiscountPatternRepository.GetNotExpireDiscountPattern();
var discountDtos = discounts?.Select(c => new SelectListItem
{
Text = c.PatternTitle,
Value = c.Id.ToString()
}).ToList();
ViewData["DiscountPatterns"] = discountDtos;
var serials =
UnitOfWork.ChargeCardSerialRepository.GetNotAssignedSerials();
var serialDtos = serials?.Select(c => new SelectListItem
{
Text = c.SerialNumber.ToString(),
Value = c.Id.ToString()
}).ToList();
ViewData["ChargeSerials"] = serialDtos;
ViewData["CardSerialCount"] =
UnitOfWork.GiftCardSerialRepository.GetNotUsedGiftSerials();
return View();
}
[HttpPost]
[Route("CreateCardGroup")]
public ActionResult CreateCardGroup(CardGroupCreateDto dto)
{
if (!ModelState.IsValid)
return View(dto);
if(!UnitOfWork.DiscountPatternRepository
.IsCardGroupDateInRange(dto.DiscountPatternId,
dto.ActiveFromDate, dto.ActiveToDate))
{
ModelState.AddModelError("ActiveFromDate", #"Error In Date.");
return View(dto); <---Problem Here
}
var group = dto.LoadFrom();
var insertedId = UnitOfWork.CardGroupRepository.Add(group);
foreach (var rangeDto in group.CardGroupGiftSerialRanges)
{
for (var i = rangeDto.GiftCardSerialBegin; i <=
rangeDto.GiftCardSerialEnd; i++)
{
var serial =
UnitOfWork.GiftCardSerialRepository.GetBySerial(i);
if (serial != null)
{
serial.CardGroupGiftSerialRangeId = rangeDto.Id;
serial.DiscountPatternId = group.DiscountPatternId;
UnitOfWork.Complete();
}
}
}
return Redirect("/CardGroup");
}
From this article:
ViewData
ViewData is a property of ControllerBase class.
ViewData is used to pass data from controller to corresponding view
Its life lies only during the current request. If redirection occurs, then its value becomes null. It’s required typecasting for getting data and check for null values to avoid error.
So what's happening is once you've done your post back to the server, you're now in a different request, meaning, that you need to repopulate your ViewData items so that their values are populated again, or else they'll be null.
So I'd recommend refactoring your Dropdown population method into a private method on your controller and then call that method in your post when you find a validation error or are just returning by calling return View(dto).
If they're used in other controllers, you can add them to a LookupService or LookupRepository or even a general helpers class that contains your lookup logic (whatever fits into your UnitofWork pattern the best for you), to make them available to those other controllers, instead of having it as a private method as per my example.
So something like this for example:
[HttpGet]
[Route("CreateCardGroup")]
public ActionResult CreateCardGroup()
{
PopulateCreateCardGroupLookups();
return View();
}
[HttpPost]
[Route("CreateCardGroup")]
public ActionResult CreateCardGroup(CardGroupCreateDto dto)
{
if (!ModelState.IsValid)
{
PopulateCreateCardGroupLookups();
return View(dto);
}
if(!UnitOfWork.DiscountPatternRepository
.IsCardGroupDateInRange(dto.DiscountPatternId,
dto.ActiveFromDate, dto.ActiveToDate))
{
ModelState.AddModelError("ActiveFromDate", #"Error In Date.");
PopulateCreateCardGroupLookups();
return View(dto); <---Problem Here
}
var group = dto.LoadFrom();
var insertedId = UnitOfWork.CardGroupRepository.Add(group);
foreach (var rangeDto in group.CardGroupGiftSerialRanges)
{
for (var i = rangeDto.GiftCardSerialBegin; i <=
rangeDto.GiftCardSerialEnd; i++)
{
var serial =
UnitOfWork.GiftCardSerialRepository.GetBySerial(i);
if (serial != null)
{
serial.CardGroupGiftSerialRangeId = rangeDto.Id;
serial.DiscountPatternId = group.DiscountPatternId;
UnitOfWork.Complete();
}
}
}
return Redirect("/CardGroup");
}
private void PopulateCreateCardGroupLookups()
{
var discounts =
UnitOfWork.DiscountPatternRepository.GetNotExpireDiscountPattern();
var discountDtos = discounts?.Select(c => new SelectListItem
{
Text = c.PatternTitle,
Value = c.Id.ToString()
}).ToList();
ViewData["DiscountPatterns"] = discountDtos;
var serials =
UnitOfWork.ChargeCardSerialRepository.GetNotAssignedSerials();
var serialDtos = serials?.Select(c => new SelectListItem
{
Text = c.SerialNumber.ToString(),
Value = c.Id.ToString()
}).ToList();
ViewData["ChargeSerials"] = serialDtos;
ViewData["CardSerialCount"] =
UnitOfWork.GiftCardSerialRepository.GetNotUsedGiftSerials();
}

How can I resolve this error 'IEnumerable<SelectListItem>' in the drop down list MVC [duplicate]

This question already has answers here:
The ViewData item that has the key 'XXX' is of type 'System.Int32' but must be of type 'IEnumerable<SelectListItem>'
(6 answers)
Closed 6 years ago.
In my MVC project I pass list of currencies to the view within the drop down list. However, once I try to post the view I get the following exception:
The ViewData item that has the key 'FromCurrencyId' is of type
'System.Int32' but must be of type 'IEnumerable'.
Currency Controller
namespace Project.Controllers
{
public class CurrencyController : Controller
{
[HttpGet]
// GET: Currency
public ActionResult Index()
{
CurrenciesClient Cur = new CurrenciesClient();
var listCurrency = Cur.findAll().ToList();
Currencies model = new Currencies();
model.FromCurrencies = new SelectList(listCurrency, "Id", "CurrencyName");
model.ToCurrencies = new SelectList(listCurrency, "Id", "CurrencyName");
return View(model);
}
[HttpPost]
public ActionResult Index(Currencies cur)
{
if (ModelState.IsValid)
{
if (cur.FromCurrencyId == cur.ToCurrencyId)
{
//do something if same currecnies and return.
ModelState.AddModelError("CurrencyCountry", "Can't make the conversion for the same value");
}
else
{
some code .....
}
}
return View(cur);
}
}
}
Currencies VM
namespace Project.ViewModels
{
public class Currencies
{
public int Id { get; set; }
[Required]
public int FromCurrencyId { get; set; }
public SelectList FromCurrencies { get; set; }
[Required]
public int ToCurrencyId { get; set; }
public SelectList ToCurrencies { get; set; }
public string CurrencyName { get; set; }
public string CurrencyCountry { get; set; }
[Required]
public decimal ConversionRate { get; set; }
public decimal Value { get; set; }
public SelectList AvailableCurrencies { get; set; }
}
}
CurrencyClient web service VM
namespace Project.ViewModels
{
public class CurrenciesClient
{
private string base_Url = "http://localhost:51646/api/";
public IEnumerable<Currencies> findAll()
{
try
{
HttpClient client = new HttpClient();
client.BaseAddress = new Uri(base_Url);
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
HttpResponseMessage response = client.GetAsync("currencies").Result;
if (response.IsSuccessStatusCode)
{
var resposeData = response.Content.ReadAsStringAsync().Result;
var Currency = JsonConvert.DeserializeObject<List<Currencies>>(resposeData);
return Currency;
}
return null;
}
catch
{
return null;
}
}
}
}
Index View
model Project.ViewModels.Currencies
#{
ViewBag.Title = "Index";
}
#using (Html.BeginForm("Index", "Currency", FormMethod.Post))
{
#Html.TextBoxFor(m => m.ConversionRate, new { #size = "5" })
#Html.DropDownListFor(m => m.FromCurrencyId, Model.FromCurrencies as IEnumerable<SelectListItem>)
#Html.DropDownListFor(m => m.ToCurrencyId, Model.ToCurrencies as IEnumerable<SelectListItem>)
<button type="submit" class="btn btn-primary">Convert</button>
}
This problem is becuase you are passing null value from your dropdown.Means you are not selecting any value. Check that if you will pass some value from dropdown it will work fine.To solve this problem you need to add the same
code
Currencies model = new Currencies();
model.FromCurrencies = new SelectList(listCurrency, "Id", "CurrencyName");
model.ToCurrencies = new SelectList(listCurrency, "Id", "CurrencyName");
in your controller index post method.Because if selectListitem would be null the following code will be executed
IEnumerable<SelectListItem> selectList = o as IEnumerable<SelectListItem>;
if (selectList == null)
{
throw new InvalidOperationException(String.Format(CultureInfo.CurrentCulture,
MvcResources.HtmlHelper_WrongSelectDataType,
name, o.GetType().FullName, "IEnumerable<SelectListItem>"));
}
Which will throw exception.
(It would be better if you will use
ViewBag.FromCurrencies = new SelectList(listCurrency, "Id", "CurrencyName");
like this.)
The better description is given here:
The ViewData item that has the key 'XXX' is of type 'System.Int32' but must be of type 'IEnumerable<SelectListItem>'
The whole explanation is also given in this link like how does this code work.
you will have to set the Dropdown data in the post controller as well, otherwise it will not be able to find the ViewBag values, as a result when after post action it calls the Index view, the ViewBag.FromCurrencies and ViewBag.ToCurrencies will be null which is obviously that we don't want.
For fixing the error, you will have to change you post action to be like:
[HttpPost]
public ActionResult Index(Currencies cur)
{
if (ModelState.IsValid)
{
if (cur.FromCurrencyId == cur.ToCurrencyId)
{
//do something if same currecnies and return.
ModelState.AddModelError("CurrencyCountry", "Can't make the conversion for the same value");
}
else
{
some code .....
}
}
CurrenciesClient Cur = new CurrenciesClient();
var listCurrency = Cur.findAll().ToList();
Currencies model = new Currencies();
model.FromCurrencies = new SelectList(listCurrency, "Id", "CurrencyName");
model.ToCurrencies = new SelectList(listCurrency, "Id", "CurrencyName");
return View(cur);
}
You should not return the view from your POST action or you will run into many issues. Here is the problem with that:
When you submit the form the URL is pointing to your Index. The body of your http post will have a bunch of Currencies items in it. Therefore, MVC, will submit the form to your Index method with Currencies cur parameter.
If all is well you return the same view.
If you refresh the page, your browser will simply reissue the last request and guess what, it will submit the form again. But this is not what you intended by refreshing. You wanted to get the form as it was originally presented not resubmit it.
Therefore instead of returning a view from a POST, you should always, except if AJAX was used, return a redirect.
In this case if all goes well, you may want to send the user to a success page or some other page so you should do this:
return RedirectToAction("YourActionName", "YourControllerName");
This pattern is called the PRG pattern. What this does is this:
user submits a form
If all goes well on the server side, you tell the browser to issue another request and get another page.
The browser gets the other page which may be a success page.
Now the user is on the success page. If they hit refresh they will get the success page again. They will not be submitting the same form over and over.

How can I capture the #Html.DropDownListFor selected value?

I am using MVC5, Razor, Entity Framework, C#. I am trying to pass a value of a dorpdown list using a link.
my model is
public class TestVM
{
public string TheID { get; set; }
}
I am loading an enum into a IEnumerable<SelectListItem>.
My enum is
public enum DiscountENUM
{
SaleCustomer,
SaleCustomerCategory,
SaleProduct,
SaleProductCategory,
SaleCustomerAndProduct,
SaleCustomerAndProductCategory,
SaleCustomerCategoryAndProductCategory,
PurchaseVendor,
PurchaseVendorAndProduct,
PurchaseVendorAndProductCategory,
PurchaseProduct,
PurchaseProductCategory,
Unknown
}
I am using the index method of the home controller
public ActionResult Index()
{
ViewBag.ListOfDiscounts = SelectListDiscountENUM();
TestVM d = new TestVM();
return View(d);
}
Where I load the ListOfDiscounts using:
private IEnumerable<SelectListItem> SelectListDiscountENUM()
{
List<SelectListItem> selectList = new List<SelectListItem>();
var listOfEnumValues = Enum.GetValues(typeof(DiscountENUM));
if (listOfEnumValues != null)
if (listOfEnumValues.Length > 0)
{
foreach (var item in listOfEnumValues)
{
SelectListItem sVM = new SelectListItem();
sVM.Value = item.ToString();
sVM.Text = Enum.GetName(typeof(DiscountENUM), item).ToString();
selectList.Add(sVM);
}
}
return selectList.OrderBy(x => x.Text).AsEnumerable();
}
My create method which is called from the view is
public ActionResult Create(TestVM d, string TheID)
{
return View();
}
My Index view is
#model ModelsClassLibrary.Models.DiscountNS.TestVM
<div>#Html.ActionLink("Create New", "Create", new { TheID = Model.TheID})</div>
<div>
#Html.DropDownListFor(x => x.TheID, #ViewBag.ListOfDiscounts as IEnumerable<SelectListItem>, "--- Select Discount Type ---", new { #class = "form-control" })
</div>
The problem is in the following line in the View
<div>#Html.ActionLink("Create New", "Create", new { TheID = Model.TheID })</div>
I have tried adding a model with the name of the field as "TheID"... no luck. Also, added a string field in the parameter, no luck. I looked at the FormControl object, and there was nothing in it either! I suspect something has to be added at the Route level in the helper, but I don't know what.
Model.TheID is always null. Even when I select an item in the DropDownListFor.
Does anyone have an idea how I can capture the select value of the DropDownListFor and send it into the Html.ActionLink TheID?

problems with getting data for dropdownlist

i have a dropdownlist getting data from data base
it contains 4 data :
my controller :
public ActionResult Index()
{
ViewBag.Postes = _db.Postes.ToList();
return View();
}
[HttpPost]
public ActionResult Index( Poste poste,string num_cin="R346399")
{
if (ModelState.IsValid)
{
if (poste.Id == 3)
{
return RedirectToAction("Inscription", "Candidat");
}
return View(poste);
}
}
my view
#Html.DropDownListFor(model => model.Id,
new SelectList(ViewBag.Postes, "Id", "intitule_poste"),"choisir le poste")
the problem that if i choose a value from dropdownlist that !=3 it's give me an error "that items must be not null "
You view includes #Html.DropDownListFor() which is generated based on the value of ViewBag.Postes. When you return the view (i.e. when poste.Id is not equal to 3) you must reassign the value of ViewBag.Postes
if (ModelState.IsValid)
{
if (poste.Id == 3)
{
return RedirectToAction("Inscription", "Candidat");
}
ViewBag.Poste = _db.Postes.ToList(); // reassign collection for dropdown
return View(poste);
}

Is there some proper way to add model error for IEnumerable model field to highlight specific input in view but not all of them?

Well I have some view model for, let's say, product:
public class ProductsAddModel
{
public IEnumerable<string> Names {get;set;}
}
I need to add several product names at a time but if there already exists product with one of the inputed names i want to add model error for Names and highlight the proper input in view. It basically looks like:
[HttpPost]
public ActionResult(ProductsAddModel model)
{
if(ModelState.IsValid)
{
var existedProducts = productRepository.AllProducts;
if(model.Names.Any(n => existedProducts.Select(p => p.Name).Contains(n)))
{
ModelState.AddModelError("Names", "Error");
return View(model);
}
}
}
But in this case all Names inputs will be highlighted in view. How can I highlight specific name input - input which causes error. I have several solutions such ass past error Name with view data to view add add error class to text input if it's value matches error name. Something like:
var match = existedProducts.Select(p => p.Name).FirstOrDefault(n => model.Names.Contains(n));
if(match != null)
{
ModelState.AddModelError("Names", "Error");
ViewBag.ErrorName = match.Name;
return View(model);
}
And in view:
for (int i; i<Model.NumberOfInputs; i++)
{
var value = Model.Names != null && Model.Names.Count() < index ? Model.Names.ToArray()[index] : string.Empty;
var errorClass = value.Equals(ViewBag.ErrorName) ? "error" : string.Empty;
<input type="text" name="Names" value="#value" class="#errorClass" />
}
... or create partial view for each input, save error name to temp data (not to view data to be able to sent this value through actions) with unique key, create render action for each input partial view, generate input id (or some custom data attribute) and temp data error key by the same pattern, check is there error for this input in temp data and add model error for "Names" if there is. Something like below:
var match = existedProducts.Select(p => p.Name).FirstOrDefault(n => model.Names.Contains(n));
if(match != null)
{
ModelState.AddModelError("Names", "Error");
var key = string.Format("Names-{0}", match.Name);
TempData[key] = "Error"; // or just true
return View(model);
}
View:
for (int i; i<Model.NumberOfInputs; i++)
{
var value = Model.Names != null && Model.Names.Count() < index ? Model.Names.ToArray()[index] : string.Empty;
Html.RenderAction("RenderInputAction", new {name = value});
}
And additional action I was talking about:
public PartialViewResult RenderInputAction(string name)
{
var key = string.Format("Names-{0}", name);
if(TempData[key])
{
ModelState.AddModelError("Names", "Error");
}
return PartialView("NameInput", name);
}
and partial view itself:
<input type="text" name="Names" value="#Model" />
As you can see these are robust and strange approaches. I just want to know is there some proper and simpler way in MVC framework to handle such complex models and their model state errors? Maybe some prefix for name attribute to catch specific input or something like that. I'm just not even sure what to google to find the answer. Thanks in advance.
Nice question! I wasn't sure.. so I tried it. Here's what I found.
My test had a model like this:
public class TestModel {
public IList<string> Items { get; set; }
public TestModel()
{
Items = new List<string>() { "Simon", "Whitehead" };
}
}
..a View like this:
#model MvcApplication1.Models.TestModel
#using (Html.BeginForm())
{
for (var i = 0; i < Model.Items.Count; i++)
{
<p>
#Html.TextBoxFor(x => Model.Items[i])
</p>
<p>#Html.ValidationMessageFor(x => Model.Items[i])</p>
}
<input type="submit" />
}
This renders the names Items[0] and Items[1]. Basically, you add a model error with the same names:
[HttpPost]
[ActionName("Index")]
public ActionResult Index_Post(TestModel model)
{
for (int i = 0; i < model.Items.Count; i++)
{
if (string.IsNullOrEmpty(model.Items[i]))
{
// empty name is Items[i]
ModelState.AddModelError(string.Format("Items[{0}]", i), "Required");
return View(model);
}
}
return null;
}
Works fine for me. Individual boxes are required when blank. The result is:

Categories

Resources