After reading this very helpful answer I modified a pair of methods to allow them both to accept the same view model:
[ActionName("AddressCorrection"), HttpGet]
public IActionResult AddressCorrectionGet(AddressCorrectionViewModel model)
{
return View(model); // was return View();
}
[ActionName("AddressCorrection"), HttpPost]
[ValidateAntiForgeryToken]
public IActionResult AddressCorrectionPost(AddressCorrectionViewModel model)
{
if (ModelState.IsValid)
{
return View("Index", new ApplicationViewModel { SuccessMessage = "That worked." });
}
model.ErrorMessage = "Something went wrong";
return View(model);
}
Problem is, calling return View(model); in AddressCorrectionGet now treats the invocation as a POST of sorts. Specifically, the validators on AddressCorrection.cshtml run. Instead of seeing a blank form ready for input, I see a form with a bunch of “required fields missing” messages.
How do I prevent the View from running the validators in this case? Or in the more general case, how does the View know that it should vs. should not run validators (I was thinking this was simply based on whether the Request method was GET vs POST. Clearly wrong.), and how can I explicitly tell the view to run or not run the validators?
Use ModelState.Clear(); before return View(model);
Related
I have got a submit button for a form, this controller actionresult method gets called when the button is pressed. Depending on the return of the _shortUrlProcessor.CreateShortURL method. I want to either display a message in red or a create a link under the mentioned submit button. What is the proper way of handling this in MVC? (see comments in code for more clarification as well)
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult ShortenURL(ShortURLModel model)
{
if(ModelState.IsValid)
{
if(_shortUrlProcessor.CreateShortURL(model.originalURL, model.shortURL))
{
ViewBag.ShortenURLSuccess(model.shortURL); //< ---- send as a localhost:port/model.shortURL link
}
else
{
ViewBag.ShortenURLSuccess("Could not create link"); //<----send as a text label (which would be shown in something like a <div/>)
}
}
return View();
}
Putting this in the view is the way to go, and here is an example using the viewbag approach:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult ShortenURL(ShortURLModel model)
{
if(ModelState.IsValid)
{
if(_shortUrlProcessor.CreateShortURL(model.originalURL, model.shortURL))
{
ViewBag.ShortenURLSuccess=true;
ViewBag.ShortenURL=model.shortURL;
}
else
{
ViewBag.ShortenURLSuccess=false;
}
}
return View();
}
And then in the view:
#if (ViewBag.ShortenURLSuccess)
{
Go here
}
else
{
<div class="error">Could not create link</div>
}
If you need more information for the url or the message, you can put those in viewbag variables as well.
it is worth noting that a more popular way to do this is with model binding, but this approach works fine for what it is. You can use the submitted model and move away from the viewbag to be a bit more efficient.
I need to display success msg when user registered. below i attached code. that code not displaying success msg. whats wrong. please help. (This is Asp.net core MVC web application)
[HttpPost]
public ActionResult Register(UserAccount user)
{
if (ModelState.IsValid)
{
_context.UserAccounts.Add(user);
_context.SaveChanges();
ModelState.Clear();
ViewBag.Message = "Successfully Registration Done";
}
return View();
}
Anywhere in your HTML page you can use the viewbag.
Example:
<h4>#ViewBag.Message</h4>
You shouldn't be using ViewBag at all for this. Use TempData instead and follow the PRG (post-redirect-get) pattern.
[HttpPost]
public ActionResult Register(UserAccount user)
{
if (ModelState.IsValid)
{
_context.UserAccounts.Add(user);
_context.SaveChanges();
TempData["Message"] = "Successfully Registration Done";
return RedirectToAction("Foo");
}
return View();
}
On success, you need to redirect the user somewhere else. It makes no sense to return them to the registration form, after they've already registered, and it solves the issue of having to clear the model state. You use TempData to store the message, so it persists through the next request. Then, in your view:
#TempData["Message"]
Where you want it to display.
I currently have 4 action methods:
UserCreate - Get
UserCreate - Post
UserEdit - Get
UserEdit - Post
My goal is to somehow combine them do just be UserEdit. The only difference between the two is that UserCreate asks for Password and ConfirmPassword in the form. UserEdit does not. I ended up having to create 2 separate ViewModels for this: UserCreateViewModel, UserEditViewModel.
// GET: Users/Create
[Route("users/create")]
public IActionResult UserCreate()
{
return View();
}
// POST: Users/Create
[Route("users/create")]
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> UserCreate(UserCreateViewModel vm)
{
}
// GET: Users/Edit/5
[Route("users/edit/{id}")]
public async Task<IActionResult> UserEdit(string id)
{
return View(vm);
}
// POST: Users/Edit/5
[Route("users/edit/{id}")]
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> UserEdit(UserEditViewModel vm)
{
return View(vm);
}
If keeping them separate is better and more MVC convention that is fine, but I just want to make sure there isn't some feature of MVC that I am not utilizing that can help me have only 2 Action methods, One view model for this.
Keep them separate. You aren't missing anything in MVC that is meant for working 'around' this so to say. Sure you could make it work but it strays from common api implementation for the sake of thinking they are similar actions. If it feels really bad you could use the same view model and code validations into page and server side (you couldn't rely on it being on the model as validation would now require two scenarios for one model) but if also split it out into two. It's cleaner.
Yes you can certainly have only 2methods using mvc feature:
You can create a method for create or edit user for GET type
[HttpGet]
[Route("users/useraddoredit/{id?}")]
public async Task<IActionResult> UserEdit(string id)
{
if(string.isNullorEmpty(id))
{
return View();
}
return View(vm);
}
You can create a method for POST type. Here create a new model UserViewModel which has all the attributes.
[Route("users/edit/{id}/{isUserEdit?}")]
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> UserEdit(UserViewModel vm, bool isUserEdit)
{
if(isUserEdit)
{
return View(vm);
}
else
{
//logic for add user
}
}
PS:But its not recommended to use this approach.
So i have a model with two models in it so that i can render to different views on the same view, one login view and one register view.
This is my model:
public class SignUpLoginModel
{
public LoginModel loginmodel { get; set; }
public RegisterModel registermodel { get; set; }
}
And when i am trying to register a new user with this controller method:
public ActionResult Register(SignUpLoginModel model)
{
if (ModelState.IsValid)
{
// Attempt to register the user
try
{
WebSecurity.CreateUserAndAccount(model.registermodel.UserName,
model.registermodel.Password,
new
{
FirstName = model.registermodel.Firstname,
LastName = model.registermodel.Lastname,
Country = model.registermodel.Country,
City = model.registermodel.City,
Birthdate = model.registermodel.Birthdate
});
WebSecurity.Login(model.registermodel.UserName, model.registermodel.Password);
return RedirectToAction("Index", "Home");
}
catch (MembershipCreateUserException e)
{
ModelState.AddModelError("", ErrorCodeToString(e.StatusCode));
}
}
// If we got this far, something failed, redisplay form
return RedirectToAction("Register", "Home", model);
}
So the modelstate.isvalid failes as it should and it redirects to the home controller and the register method and send the model. Here everything works fine, the model contains the data. But when i redirect to another controller
public ActionResult Register(SignUpLoginModel model)
{
ViewBag.Message = "RegisterFail";
return View("Index", "Home", model);
}
my model ends up with null instead of the right data. What am i doing wrong?
I'm pretty sure it's because RedirectToAction (neither the overload you are using nor any of the other signatures) doesn't forward your model for you. It sends a 302 back to your browser which then issues a new GET request to a new url - therefore your model data is lost.
So you'd need to do something like this in the first controller
{
TempData["signupModel"]=model;
return RedirectToAction("Register", "Home", model);
}
and then this in the redirect controller:
public ActionResult Register(SignUpLoginModel model)
{
ViewBag.Message = "RegisterFail";
return View("Index", "Home", TempData["signupModel"]);
}
But what I don't understand is why you don't just directly return a view the first time around, why do you need to redirect - because from your code you seem to be just redirecting back to the same controller - or have I missed something?
Edit based on comments below
I honestly think you would be better off using Ajax for this because what you're trying to do here is maintain state across multiple controllers/views which is not really idiomatic for MVC.
If you submit the "register" popup form via Ajax, then you never actually leave the Index page, you just execute some code server-side and return some JSON to indicate if your registration was OK or not. When this response indicates an error you can just display an error message on the popup form or whatever, otherwise you can redirect to wherever on the client-side.
OR
When your Register controller decides that registration has failed, return the Home page index view e.g
ViewBag.Message = "RegisterFail";
return View("~/HomePage/HomeController/Index.cshtml", model); //or whatever the virtual path to the home page view is
However I don't like this approach as it sounds to me like you have your register form HTML being generated in-line by the the Home page view, which sounds like mixed concerns to me.
I have an "Add to Cart" button that if the browser supports JS + Ajax (and doesn't have it turned off) it POSTS using Ajax back to the site, however if they don't support it, or have it turned off it does the manual style POST.
What I am hoping to accomplish is two views - one when the user posts back using a regular POST and one when it comes from a AJAX POST. That way I can show an in-line message (partial) or a full screen.
I would prefer not having to duplicate the controller/action code twice, it just seems non-elegant.
Is there any recommended solutions or patterns for this type of issue?
John,
You can use the IsAjaxRequest method on the request to determine this. You would apply it to your scenario thusly:
public ActionResult AddToCart(YourCartViewmodel cartViewmodel)
{
if (ModelState.IsValid)
{
// do the standard/common db stuff here
if(Request.IsAjaxRequest())
{
return PartialView("myPartialView");
}
else
{
return View("standardView");
}
}
/* always return full 'standard' postback if model error */
return View(cartViewmodel);
}
altho not perhaps giving a complete solution, this should give you a good start...
You can have two different actions in your controller. One for regular post and one for AJAX.
public ActionResult AddToCart(Viewmodel vm)
{
if (ModelState.IsValid)
{
DoStuff(vm);
return View("ViewForRegularPost");
}
/* error */
return View(vm);
}
and
public ActionResult JsonAddToCart(Viewmodel vm)
{
if (ModelState.IsValid)
{
DoStuff(vm);
return View("ViewForJS");
}
/* error */
return View(vm);
}
Instead of repeating your controller code, have a separate method for actual controller code.
public void DoStuff(Viewmodel vm)
{
//TODO : Actual controller code goes here
}