Why do I get this 404 error? - c#

In a newly created MVC4 application, insert this function in the account controller
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult AdminLogin(AdminLoginModel model, string returnUrl)
{
if (ModelState.IsValid && WebSecurity.Login("administrator", model.Password, persistCookie: model.RememberMe))
{
return RedirectToLocal(returnUrl);
}
// If we got this far, something failed, redisplay form
ModelState.AddModelError("", "The password provided is incorrect.");
return View(model);
}
and this
public class AdminLoginModel
{
[Required]
[DataType(DataType.Password)]
[Display(Name = "Password")]
public string Password { get; set; }
[Display(Name = "Remember me?")]
public bool RememberMe { get; set; }
}
is put into the accountModel.cs. I also created a new file AdminLogin.cshtml and leave it empty.
In the _loginPartial.cshtml file I insert an action link
<li>#Html.ActionLink("Register", "AdminLogin", "Account", routeValues: null, htmlAttributes: new { id = "registerLink" })</li>
But when I click that Register link, I then see the 404 error stating that
/Account/AdminLogin is not found.
I miss something along the way inserting that tiny mvc; could you help me ? I am a mvc beginner.

Clicking a link in a browser results in a GET request, but your action method is only available for POST requests.
Add the [HttpGet] attribute or remove the [HttpPost] attribute to resolve this particular problem.
In general you will want to keep using POST requests when submitting data. As such, my recommendation would be to change the client side to use a form (or use client side logic to intercept the link click action and submit the data using an ajax request).

Related

MVC Value not posting back to controller

This is weird, or at least i think. I have a simple password change form, which has a control for taking the user input for the password. Now, the problem is whatever the user enters, the view only posts back an empty string.
This is the control on my view
#Html.PasswordFor(model => model.Password)
Model attribute:
[Required]
[DataType(DataType.Password)]
[StringLength(20, MinimumLength = 10)]
[Display(Name = "Password")]
public string Password { get; set; }
Code:
[HttpGet]
public ActionResult Change(int userid)
{
try
{
var model = GetUser(userid);
return View(model);
}
catch (Exception e)
{
//log error
throw;
}
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Change(User model)
{
try
{
model = ChangeUserCredentials(model); //break point here
return View(model);
}
catch (Exception e)
{
//log error
throw;
}
}
What I have tried:
removed caching in IE, firefox and chrome
tried changing 'passwordfor' to 'editorfor'
fwiw, it was working fine until a few days back, nothing changed in the view or the code in the controller.
add a parameter to your password helper like this:
#Html.PasswordFor(model => model.Password,new{#name="mypassword"})
then add this parameter to your action method
public ActionResult Change(User model,string mypassword)
this will work! but it's not a really good way.
if you want to do it right you must be sure you did Model Binding right.
I know this sounded stupid. But this is what fixed the issue, just in case anyone else faces the same:
1) Per this post , I added new { value = Model.CurrentPassword }) to the password attribute in the view
2) I am deliberately setting the password to 'null' in the GET method

Editing a model in asp.net mvc

I'm developing a simple site using ASP.NET MVC 5 and I have some issue with editing a model.
Say, the model is as follows:
public class Model
{
public string Login { get; set; }
public string Password { get; set; }
}
I have two different user roles, for example, Admin and User. Now I'd like User to be able to edit only Login, whilst Admin should be able to edit both properties. I'm using pretty standard edit approach, something like this:
[HttpGet]
public ActionResult Edit(int id)
{
return View(FindModel(id));
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit(Model model)
{
if (ModelState.IsValid)
{
UpdateModel(model);
return RedirectToAction("Index");
}
return View(model);
}
The ideas I have:
I can show the Password text field only if a user is Admin so that User won't be able to change it. However, as far as I know, MVC binding uses the id (or name, probably) of an element to bind it to a model, so User will be able to manually create a field for password modifying HTML directly and then send the data to the controller.
I can create two different Models for Admin and User, say UserModel and AdminModel, two different actions in controller, and depending on the role of a user, I will create different submit buttons for User and Admin. So, User will click on 'EditUser' button and UserModel won't even content Password, therefore making it not so easy to forge the password.
I can check if the pass was changed and the user is User, an error will be shown.
Other solution is to simply divide editing into two actions: edit login and edit password separately.
Could anyone give any ideas as to how to solve this and whether or not I need to protect the application from such forgery?
There are a LOT of way to do this. To keep the code DRY I would do something like:
public class EditLoginVM
{
public bool CanEditPassword { get; set; }
public string Login { get; set; }
public string Password { get; set; }
}
In the view:
#if (Model.CanEditPassword )
{
// Show Password Textbox blah blah :)
}
Controller:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit(EditLoginVM model)
{
if (ModelState.IsValid)
{
var updateModel = new Model();
updateModel.Login = model.Login;
if (/* user is admin, don't use EditLoginVM.IsAdmin!!!! */)
{
model.Password = model.Password;
}
UpdateModel(model);
return RedirectToAction("Index");
}
model.CanEditPassword = /* reset it just in case of overposting */;
return View(model);
}

Only POSTs with both ActionLink and submit button; redirects otherwise

I'm trying to setup a simple form submission in MVC5, but I'm finding that my method doesn't get fired unless I have both an ActionLink and submit button.
I have a simple model:
public class LoginModel
{
public string username { get; set; }
}
Then I have two methods in my controller, one for when a form submission is available and one when not:
[HttpGet]
public ActionResult Login()
{
return View();
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Login(LoginModel myModel)
{
var username = myModel.username;
// do something with username
return View();
}
Finally, my View creates a POSTing form:
#using (Html.BeginForm("Login", "Home", FormMethod.Post))
{
#Html.AntiForgeryToken()
#Html.TextBox("username", string.Empty)
#Html.ActionLink("Enter", "Login")
<input type="submit" name="submit" value="Enter" />
}
I don't really care whether I use an ActionLink or whether I have a submit button (MSDN implies it should be the latter), but if I have only one of them, my [HttpPost] method is not called, and my page is redirected with the username in the query string:
/Home/Login?ReturnUrl=%2F%3Fusername%3DmyUsernameHere
If I have both on the page, I can click the ActionLink and I see that the appropriate method is called with myModel.username containing the value I provided. The submit button, however, will still redirect.
I want this form method to be POST, not GET (which it is in the generated HTML), and for failures to not contain the key as a GET param. What's the problem with having only one of these form submission mechanisms? Why do they not trigger the POST as expected? Is there something more I need to do to 'register' my model with the view, even though it is submitted properly in my workaround scenario?
Edit -- My configured routes are typically as follows:
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
name: "Default",
url: "{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
routes.RouteExistingFiles = true;
So normally, I'm going to /Login instead of /Home/Login.
Also, I do have some authentication setup, with my HomeController decorated with [Authorize] and my Login methods both with [AllowAnonymous]. When I remove all annotations, I still find that my [HttpPost] is not called, and username shows up as a GET parameter instead of being POSTed.
I believe that the application doesn't understand that you're trying to make a model with just username. So, you are sending a string username and attempting to place it into a model. I'm not entirely sure about that. Could you try binding your form to your model? Here's an example:
View:
#model YourApplication.Models.LoginModel
#using (Html.BeginForm("Login", "Home"))
{
#Html.AntiForgeryToken()
#Html.TextBoxFor(model => model.username, string.Empty)
<input type="submit" value="Enter" />
}
Controller:
[HttpGet]
public ActionResult Login()
{
return View();
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Login([Bind(Include="username")] LoginModel myModel)
{
var username = myModel.username;
// do something with username
return View("Congrats");
}
Here's an alternate option. If you wanted to do something else, maybe try accepting the string "username" and creating your model after? Example:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Login(string username)
{
LoginModel myModel = new LoginModel();
myModel.username = username;
// do something with username
return View("Congrats");
}
Either way, you should be using the submit button.

MVC 5 Remote Validation

I need to validate an input field value from user before the form is submitted.
I have created an action in my custom controller and decorated the field with it:
action name: CheckValue
controller name: Validate
[Remote("CheckValue", "Validate"), ErrorMessage="Value is not valid"]
public string Value { get; set; }
The problem is when I press submit, the form is being submitted and then the message Value is not valid is shown if the value entered by the user is not valid.
How can I validate the value entered by user and prevent the form to be submitted if value is not valid, and display the error message?
If I try in JavaScript to check if the form is valid $("#formId").valid() that returns true, that means no matter what is the status of the value (valid or not) the form is valid.
In the other hand if I decorate another field with the [Required] attribute the form is not submitted and the error is shown for that field that is required. However the validation doesn't occur behind the scene for the remote validation field.
The complete solution of Remote Validation in MVC. It will check if the email exists in database and show the following error:
Email already exists
Account Controller Action
[AllowAnonymous]
[HttpPost]
public ActionResult CheckExistingEmail(string Email)
{
try
{
return Json(!IsEmailExists(Email));
}
catch (Exception ex)
{
return Json(false);
}
}
private bool IsEmailExists(string email)
=> UserManager.FindByEmail(email) != null;
Add Model Validation
[Required]
[MaxLength(50)]
[EmailAddress(ErrorMessage = "Invalid Email Address")]
[System.Web.Mvc.Remote("CheckExistingEmail", "Account", HttpMethod = "POST", ErrorMessage = "Email already exists")]
public string Email { get; set; }
Add Scripts
<script src="#Url.Content("~/Scripts/jquery.validate.min.js")" type="text/javascript"></script>
<script src="#Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")" type="text/javascript"></script>
You dont put the Controller code. But must to be something like this:
Your code:
[Remote("CheckValue", "Validate", ErrorMessage="Value is not valid")]
public string Value { get; set; }
My code for the controller(Validate):
public ActionResult CheckValue(string Value)
{
if (Value == "x value")
{
// This show the error message of validation and stop the submit of the form
return Json(true, JsonRequestBehavior.AllowGet);
}
else
{
// This will ignore the validation and the submit of the form is gone to take place.
return Json(false, JsonRequestBehavior.AllowGet);
}
}
With the reference of c-sharpcorner demo
We can use RemoteAttribute.
Step 1
In the HomeController create a method and for that write the following.
public JsonResult IsUserExists(string UserName)
{
//check if any of the UserName matches the UserName specified in the Parameter using the ANY extension method.
return Json(!db.Users.Any(x => x.UserName == UserName) ,JsonRequestBehavior.AllowGet);
}
You might be wondering why we are returning JsonResult back. We want the validation to occur at the client side, so we are returning a JsonResult.
Step 2
The next step is to hook this method up with the username property and for that first we need to add a class file in the models folder, add a partial User class and provide the required customization to the UserName property.
using System.ComponentModel.DataAnnotations;
using System.Web.Mvc;
namespace UniqueField.Models
{
[MetadataType(typeof(UserMetaData))]
public partial class User
{
}
class UserMetaData
{
[Remote("IsUserExists","Home",ErrorMessage="User Name already in use")]
public string UserName { get; set; }
}
}
Step 3
In create.cshtml we need to specify the source of the three jQuery files in the given order.
<h2>Create</h2>
<script src="~/Scripts/jquery-1.10.2.js"></script>
<script src="~/Scripts/jquery.validate.js"></script>
<script src="~/Scripts/jquery.validate.unobtrusive.js"></script>
The existing answers are great but there are a couple of gotchas:
1) The validation method's parameter name has to exactly match the name of the property being validated, e.g. for
[System.Web.Mvc.Remote("CheckExistingDocumentTypeCode", "DocumentTypes", HttpMethod = "POST", ErrorMessage = "Code already exists")]
public string DocumentTypeCode { get; set; }
The validation method's parameter must be called DocumentTypeCode, including the capital letter, or else you'll get a null as the parameter instead of the value of the property being validated:
[AllowAnonymous]
[HttpPost]
public async Task<ActionResult> CheckExistingDocumentTypeCode(string DocumentTypeCode)
Be particularly wary of this if you are a Resharper user, of if you're writing multipurpose validation methods for use by more than one property.
2) I had to get this working with a Telerik grid and I had to implement it slightly differently in order to get the validation failure messages to show correctly in the grid (the examples here showed 'false' as the validation error message):
[AllowAnonymous]
[HttpPost]
public async Task<ActionResult> CheckExistingDocumentTypeCode(string DocumentTypeCode)
{
try
{
if (!await IsDocTypeCodeExists(DocumentTypeCode))
{
return Json(true, JsonRequestBehavior.AllowGet);
}
return Json("This Document Type Code is already in use", JsonRequestBehavior.AllowGet);
}
catch (Exception ex)
{
return Json(ex.ToString(), JsonRequestBehavior.AllowGet);
}
}
Remote Validation in MVC
Model Class must have a namespace "System.Web.Mvc" where you have defined the property.
using System.Web.Mvc;
[Required(ErrorMessage = "E-mail is required")]
[RegularExpression(#"^[a-zA-Z0-9_\.-]+#([a-zA-Z0-9-]+\.)+[a-zA-Z]{2,6}$", ErrorMessage = "Email is not valid")]
[StringLength(30, ErrorMessage = "Email must not be more than 30 char")]
[Remote("IsEmailAvailable", "User", ErrorMessage = "E-mail already in use")]
public string Email { get; set; }
Make sure you have to implement IsEmailAvailable Action on the Controller.
[HttpGet]
public JsonResult IsEmailAvailable(string email)
{
// Check if the e-mail already exists
return Json(!db.Users.Any(x => x.Email == email), JsonRequestBehavior.AllowGet);
}
Make sure you have added this js on View for client-side validation.
<script src="~/Scripts/jquery-1.10.2.min.js"></script>
<script src="~/Scripts/jquery.validate.unobtrusive.min.js"></script>
<script src="~/Scripts/jquery.validate.min.js"></script>
and also enable client-side validation from web.config
<appSettings>
<add key="ClientValidationEnabled" value="true" />
<add key="UnobtrusiveJavaScriptEnabled" value="true" />
</appSettings>
Note:
Remote attribute only works when JavaScript is enabled. If the end-user, disables JavaScript on his/her machine then the validation does not work. This is because RemoteAttribute requires JavaScript to make an asynchronous AJAX call to the server-side validation method. As a result, the user will be able to submit the form, bypassing the validation in place. This why it is always important to have server-side validation.
In case when Javascript is disabled:
To make server-side validation work, when JavaScript is disabled, there are 2 ways
Add model validation error dynamically in the controller action
method.
Create a custom remote attribute and override IsValid() method.
Adding model validation error dynamically in the controller action method. Modify the Create action method that is decorated with [HttpPost] attribute as shown below.
[HttpPost]
public ActionResult Create(User user)
{
// Check if the Email already exists, and if it does, add Model validation error
if (db.Users.Any(x => x.Email == user.Email))
{
ModelState.AddModelError("Email", "Email already in use");
}
if (ModelState.IsValid)
{
db.Users.AddObject(user);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(user);
}
At this point, disable JavaScript in the browser, and test your application. Notice that, we don't get client-side validation, but when you submit the form, server-side validation still prevents the user from submitting the form, if there are validation errors.
However, delegating the responsibility of performing validation, to a controller action method violates the separation of concerns within MVC. Ideally, all validation logic should be in the Model. Using validation attributes in MVC models should be the preferred method for validation.
I got the same problem i was copying and pasting script links >
problem solved when I drag and drop jquery files from script folder to view page
I hope this help.

MVC Razor Validation Errors showing on page load when no data has been posted

I'm messing around with data annotations. When I click on a link to go to a page, the validation messages are being displayed, but I would like to have the validation messages not show unless data has been posted.
View:
#Html.TextBoxFor(m => m.EmailAddress, new { #placeholder = "Enter Email", #class = "form-control" })
#Html.ValidationSummary(true, "Registration Failed. Check your credentials")
#Html.ValidationMessageFor(m => m.EmailAddress, "You must enter a valid Email Address.")
Model:
[Required(ErrorMessage = "Email is required")]
[DataType(DataType.EmailAddress)]
[EmailAddress]
[Display(Name = "Email Address: ")]
public string EmailAddress { get; set; }
Controller:
[HttpGet]
public ActionResult AddUser()
{
return View();
}
[HttpPost]
public ActionResult AddUser(UserCreateViewModel user)
{
if (ModelState.IsValid)
{
var success = UserRepository.AddUser(user);
if (success)
{
return View("Success");
}
}
return View("AddUser");
}
Like I said, my problem occurs on page load of the AddUser view. When I click on the link to view the AddUser page, validation messages are showing after it loads, yet at this point no data has been posted and the model is empty.
You can clear model state after binding user:
ModelState.Clear();
This happens because ModelBinder will set ModelState on binding.
In every action that binds a model and returns a view with the same model you will have this problem.
[HttpPost]
public ActionResult AddUser(UserCreateViewModel user)
{
if (ModelState.IsValid)
{
var success = UserRepository.AddUser(user);
if (success)
{
return View("Success");
}
}
ModelState.Clear(); // <-------
return View("AddUser");
}
Set the validation style to:
.validation-summary-valid { display:none; }
So by default it's hidden. An error will trigger it to display.
.field-validation-valid {
display: none;
}
Whenever the validation triggers on page load, this ".field-validation-valid" value is automatically added to the class attribute of the triggered input element.
By adding CSS to display none as that particular class's value, you'll no longer see the validation messages on initial page load.
The validation messages will still display normally after the particular input element has been touched.
$('.field-validation-error').html("");

Categories