ASP.NET MVC 3 - Custom Validator - c#

I am trying to implement a custom validator for phone number on an ASP.NET MVC 3 App I am writing. I have wriiten the code for the custom validator as below
public class PhoneNumberValidator : ValidationAttribute
{
public PhoneNumberValidator() : base("The Phone Number is not Valid")
{
}
public override bool IsValid(object value)
{
if (value != null)
{
string phonenumber = value.ToString();
var regex = new Regex(#"^(?:[0-9]+(?:-[0-9])?)*$");
if (regex.IsMatch(phonenumber))
{
return true;
}
else
{
return false;
}
}
return false;
}
}
Then in my Model class I have the following :
[Display(Name = "PhoneNumber")]
[Required(ErrorMessage = "Is Phone Number Required")]
[PhoneNumberValidator]
public string PhoneNumber { get; set; }
However when I run my app and click the proceed button on the page it does not throw an error if the value entered is letters although if I set a breakpoint I can see that the value is being read in to string phonenumber ok. Am I missing something simple?

You seem to be reinventing a wheel. Why not use the existing regex validator:
public class MyViewModel
{
[Display(Name = "PhoneNumber")]
[Required(ErrorMessage = "Is Phone Number Required")]
[RegularExpression(#"^(?:[0-9]+(?:-[0-9])?)*$")]
public string PhoneNumber { get; set; }
}
This being said validation is triggered by the model binder, so make sure that the controller action you are submitting the form to takes the view model as argument:
[HttpPost]
public ActionResult Process(MyViewModel model)
{
if (!ModelState.IsValid)
{
// the model is invalid => redisplay view
return View(model);
}
// at this stage the model is valid => you could do some processing here
// and redirect
...
}
or use the TryUpdateModel method (personally I prefer the previous approach though):
[HttpPost]
public ActionResult Process(FormCollection some_Dummy_Parameter_Thats_Not_Used_At_All_But_Which_We_Need_To_Avoid_The_Method_Overloading_Error_With_The_GET_Action_Which_Has_The_Same_Name)
{
var model = new MyViewModel();
if (!TryUpdateModel(model))
{
// the model is invalid => redisplay view
return View(model);
}
// at this stage the model is valid => you could do some processing here
// and redirect
...
}
Also in order to display the error message somewhere make sure that you have a corresponding placeholder in your view:
#Html.EditorFor(x => x.PhoneNumber)
#Html.ValidationMessageFor(x => x.PhoneNumber)
or use a validation summary helper:
#Html.ValidationSummary(false)

Related

I don't get back the changes of my model in my View Mvc

I am starting in asp.net Mvc and making test, i am doing a simple chat using PubNub api and i want finish it using only razor code only and one page only.
Model Chat.cs:
namespace SimpleChat.Models
{
public class Chat
{
public string NuevoMensaje { get; set; }
public string TextArea { get; set; }
}
}
View:
#model SimpleChat.Models.Chat
#using (Html.BeginForm("Index","Chat",FormMethod.Post))
{
#Html.LabelFor(model => model.NuevoMensaje, "Nuevo Mensaje")
#Html.TextBoxFor(model => model.NuevoMensaje)
<input type="submit" class="btn-default" value="Enviar" />
#Html.TextAreaFor(model => model.TextArea)
}
Controller:
static string variante = "";
public ActionResult Index()
{
pubnub.Subscribe<string>("Chat", subCallback, connecCallBack, errorCallback);
//Chat nuevochat = new Chat();
return View();
}
[HttpPost]
public ActionResult Index(Chat chat)
{
pubnub.Publish<string>("Chat", chat.NuevoMensaje, userCallback, puberror);
chat.NuevoMensaje = "";
chat.TextArea =variante;
return View("Index",chat);
}
private void subCallback(string obj)
{
string[] retorno = obj.Split(',','"');
variante += "Richard dice:" + retorno[0] + "\n";
}
When i press submit don't get the new data, why?
If you want to render the updated value of TextArea property of your view model, You should clear the model state dictionary.
Model state dictionary has the initial values of your form inputs. So when razor (re)render the same view, It gives priority to the content in model state dictionary than the view model object you passed to the view.
You can use the ModelState.Clear() method to clear the model state dictionary values before returning to the view.
chat.TextArea = variante;
ModelState.Clear();
return View("Index",chat);
Assuming your variante variable has the updated text.

MVC ModelState.IsValid=true with a null required property

I have this Model
public class ModelVM
{
private string _rD;
[Required]
public string RD
{
get
{
return _rD;
}
set
{
_rD = RCodes.Contains(value)? value : null;
}
}
private static List<string> RCodes = new List<string>
{
"OK",
"OTHER",
"ANOTHER"
};
}
In my MVC Controller
public class MyController : Controller
{
public ActionResult Index(ModelVM modelVM, FormCollection collection)
{
if (!ModelState.IsValid)
return Json(new
{
Result = "ERROR",
Message = "Missing fields."
});
return Json("OK");
}
}
I send: { RD: "Whatever" }
And in debugging ModelState.IsValid=true. I have a similar code on a WebApi Controller and works as I expect (modelstate.valid=false)
Do you have some ideas why MVC is doing that? or what is wrong with my code?
ModelState.IsValid tells you if any model errors have been added to ModelState.
In this case it is valid because there are no client side errors in the provided data that would affect ModelState.
You said...
I sent { RD: "Whatever" }
...which would mean that the model binder will look at the data sent and match the properties with the intended type. From a model binding perspective the [Required] validation was met because when the binder looked at the route value dictionary for the required property RD, it was provided by the client in the incoming data.
If you want to manually invalidate the state you can...
public ActionResult Index(ModelVM modelVM, FormCollection collection)
{
if(ModelState.IsValid) {
if(modelVM.RD == null) {
ModelState.AddModelError("RD", "RD is invalid.");
}
}
if (!ModelState.IsValid)
return Json(new
{
Result = "ERROR",
Message = "Missing fields."
});
return Json("OK");
}

Is it possible to pass additional data to error view from attribute?

I want to pass additional data from my error handler attribute to error view.
This is how I use my attribute:
[ErrorHandler (View = "Error", Title = "Some title", Text = "Some text")]
public ActionResult Delete(int id, string type, DBRepository repository){...}
This is how I pass data:
public class ErrorHandler : HandleErrorAttribute
{
public string Title { get; set; }
public string Text { get; set; }
/*Some other stuff*/
filterContext.Controller.TempData["CustomErrorTitle"] = Title;
filterContext.Controller.TempData["CustomErrorText"] = Text;
}
And this is my error view:
#model HandleErrorInfo
#{
ViewBag.Title = TempData["CustomErrorTitle"];
}
<h1 class="text-danger">#TempData["CustomErrorTitle"]</h1>
<h1 class="text-danger">#TempData["CustomErrorText"]</h1>
#if (Request.IsAuthenticated && User.IsInRole("Admin"))
{
<div>
Exception details: #Model.Exception
</div>
}
My code works, but i don't want to use TempData.
Is there other way to pass data without using TempData?
yes there's another way using json like this
in you controller
return Json(new
{
CustomErrorTitle= Title ,
CustomErrorText= Text
});
and in your aspx when you call the action write
error:function(data)
{
// you can call your error attribute like this
//data.CustomErrorTitle or data.CustomErrorText
//and do what ever you want
}

connecting controller with model to display results in view page

So i have this aps.net mvc project in which i created a service layer, model views, controller, and a view page. But i am having trouble displaying my results to the view page. I am starting this would by passing in a specific linq statement in the service layer so i should be able to return it to show up on the view. Here is what i have:
Service:
public IEnumerable<RoleUser> GetUsers(int sectionID)
{
var _role = DataConnection.GetRole<RoleUser>(9, r => new RoleUser
{
Name = RoleColumnMap.Name(r),
Email = RoleColumnMap.Email(r)
}, resultsPerPage: 20, pageNumber: 1);
return _role;
}
Models:
public partial class Role
{
public RoleView()
{
this.Users = new HashSet<RoleUser>();
}
public ICollection<RoleUser> Users { get; set; }
}
public class RoleUser
{
public string Name { get; set; }
public string Email { get; set; }
}
Controller:
public ActionResult RoleUser(RoleView rvw)
{
var rosterUser = new RosterService().GetUsers();
ViewBag.RosterUsers = rosterUser;
return View();
}
View:
<div>
<span>#Model.Name</span>
</div>
I am not sure what i am missing or doing wrong but any tips will be great. I basically want to return the results from the linq statement i am testing to see that the connection is correct and functionality is there before enhancing. Thanks...
Well, if I were to go off the code you've provided I would say that I'm unsure how this compiles:
public partial class Role
{
public RoleView()
{
this.Users = new HashSet<RoleUser>();
}
public ICollection<RoleUser> Users { get; set; }
}
it feels like that should be:
public partial class RoleView
and then I would say that at the top of your view you're missing this:
#model NamespaceToClass.RoleView
and then I would say you're not going to be able to issue this:
#Model.Name
because RoleUser isn't your model. You're going to need to loop through the users:
#foreach (RoleUser ru in Model.Users)
and then inside that loop you can build some HTML with this:
ru.Name
but I would also question your controller. Right now it's receiving a model to return that model. There is some code missing here but generally speaking, inside the method:
public ActionResult RoleUser(RoleView rvw)
you would actually go get the data, construct the model, and then return that:
var users = serviceLayer.GetUsers(...);
// now construct the RoleView model
var model = ...
return View(model);
Based off of our conversation you currently have something like this in your controller:
public ActionResult View(int id)
{
// get the menu from the cache, by Id
ViewBag.SideBarMenu = SideMenuManager.GetRootMenu(id);
return View();
}
public ActionResult RoleUser(RoleView rvw)
{
var rosterUser = new RosterService().GetUsers();
ViewBag.RosterUsers = rosterUser;
return View();
}
but that really needs to look like this:
public ActionResult View(int id)
{
// get the menu from the cache, by Id
ViewBag.SideBarMenu = SideMenuManager.GetRootMenu(id);
var rosterUser = new RosterService().GetUsers();
ViewBag.RosterUsers = rosterUser;
return View();
}
because you're launching this page from the sidebar which is hitting this action because you're passing the id in the URL. You don't even need the other action.

Client-side custom data annotation validation

I've create a custom data annotation to do some validation on my view model. The problem is that it doesn't validate on the client-side. Here's my model:
public class MemberViewModel
{
[ScaffoldColumn(false)]
public int MemberId { get; set; }
[Required(ErrorMessage = "Name is required")]
public string Name { get; set; }
//My custom data annotation
[EnforceTrue(ErrorMessage = "You must agree to the Terms and Conditions")]
public bool AgreeTerms { get; set; }
}
My data annotation validation code:
public class EnforceTrueAttribute : ValidationAttribute, IClientValidatable
{
public EnforceTrueAttribute() { }
public override bool IsValid(object value)
{
return value != null && (bool)value == true;
}
public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
{
yield return new ModelClientValidationRule() { ValidationType = "enforcetrue", ErrorMessage = this.ErrorMessageString };
}
}
My controller method:
[HttpPost]
public ActionResult Index(MemberViewModel viewModel)
{
Member member = new Member();
TryUpdateModel(member);
if (ModelState.IsValid)
{
_membersRepository.SaveMember(member);
return RedirectToAction("Index", "Home");
}
return View(viewModel); // validation error, so redisplay same view
}
And my view:
#using (Html.BeginForm("Index", "Members", FormMethod.Post)) {
#Html.HiddenFor(m => m.MemberId)
<div class="editor-label">#Html.LabelFor(model => model.Name)</div>
<div class="editor-field">#Html.TextBoxFor(model => model.Name)</div>
<div class="editor-field">#Html.CheckBoxFor(model => model.AgreeTerms) <label for="AgreeTerms">I agree to the Terms and Conditions</label></div>
<p>
<input type="submit" value="Submit" />
</p>
#Html.ValidationSummary()
}
So all my other error messages get displayed in the validation summary with client-side validation. But for my custom data annotation, the error message doesn't show until the rest of the model is valid, and after you submit the form and page reloads, that's when the error is displayed in the summary.
Is there something else I need to do here to get it to show up in the summary with the other errors?
I'm using C# and ASP.NET MVC 3
Had same issue recently. You can write:
$.validator.addMethod('enforcetrue', function (value, element) {
return $(element).is(":checked");
});
$.validator.unobtrusive.adapters.add('enforcetrue', [], function (options) {
options.messages['enforcetrue'] = options.message;
options.rules['enforcetrue'] = options.params;
});
Similar question here ASP.NET MVC 3 client-side validation
Implementing Iclientvalidatable only adds unobtrusive attributes to generated html inputs. To enable validation on client side you must write validators that use these unobtrusive attributes to validate the inputs. Here you can find very good explanation of client and server validation in asp.net mvc 3
A Remote Validator is what you need here is the link
http://www.devtrends.co.uk/blog/the-complete-guide-to-validation-in-asp.net-mvc-3-part-1

Categories

Resources