MVC3 two partial views in one view - asp.net-mvc

I am trying to learn MVC 3 & Razor and I'm stuck here for 3 hours now. This is what I have
MVC project created using the default template with Account registration and all that good stuff from the template. What I'm trying to do is have both registration page and login page in the index of the HomeController so I created a partial view for both Register (_RegisterPartial) and LogOn (_LogOnPartial). When I go to the index page, I see the registration and login forms which is good but when I try to login or register it goes into an infinite loop.
My HomeController looks like this;
// **************************************
// Registration
// **************************************
public ActionResult DoRegister()
{
return PartialView("_Register");
}
[HttpPost]
public ActionResult DoRegister(RegisterModel model)
{
if (ModelState.IsValid)
{
// Attempt to register the user
MembershipCreateStatus createStatus = MembershipService.CreateUser(model.UserName, model.Password, model.Email, model.UserProfile);
if (createStatus == MembershipCreateStatus.Success)
{
FormsService.SignIn(model.UserName, false); // createPersistentCookie
return View("Success");
}
else
{
ModelState.AddModelError("", AccountValidation.ErrorCodeToString(createStatus));
}
}
// If we got this far, something failed, redisplay form
ViewBag.PasswordLength = MembershipService.MinPasswordLength;
return View(model);
}
// **************************************
// Login
// **************************************
public ActionResult DoLogin()
{
return PartialView("_Login");
}
[HttpPost]
public ActionResult DoLogin(LogOnModel model, string returnUrl)
{
if (ModelState.IsValid)
{
if (MembershipService.ValidateUser(model.UserName, model.Password))
{
// logged in
FormsService.SignIn(model.UserName, model.RememberMe);
if (Url.IsLocalUrl(returnUrl))
{
Redirect(returnUrl);
}
else
{
View("Success");
}
}
else
{
// Not logged in
ModelState.AddModelError("", "The user name or password provided is incorrect.");
}
}
// If we got this far, something failed, redisplay form
return View("Success");
}
and my cshtml looks like this;
#{
ViewBag.Title = "Home Page";
Layout = "~/Views/Shared/_Layout.cshtml";
}
#if (Request.IsAuthenticated)
{
#Html.ActionLink("Log Off", "LogOff", "Account")
}
else
{
Html.RenderAction("DoLogin");
Html.RenderAction("DoRegister");
}
Regards,
Ryan

Do you read exception messages?
A public action method 'Register' was not found on controller 'AudioRage.Controllers.HomeController'
Now look at the code of HomeController you've posted. Do you see a Register action on it? I don't.
So add one:
public ActionResult Register()
{
...
}
In your HomeController you have an action called Register but action is only accessible through POST verbs as it is decorated with the [HttpPost] attribute:
[HttpPost]
[ActionName("Register")]
public ActionResult Index(RegisterModel model)
so you cannot invoke it with a GET verb on /Home/Register.

I can't exactly replicate your situation but I would say that you partial forms are not posting correctly. Have a look a rendered html of the page and check where the forms are being posted to. My guess is that they are being posted to the Index action. Coupled with the redirect, I think that is where the infinite loop is coming from.
My guess is the html rendered for both forms are similar and posting to the same action i.e. <form action="/" method="post">, since they are being rendered by the HomeController's Index action.
Change the partial forms (_Login.cshtml and _Register.cshtml) and explicitly state which action/controller combination to post to (more on Html.BeginForm from MSDN)
#using (Html.BeginForm("DoLogin","Home")) {/*snipped*/} //in _Login.cshtml
#using (Html.BeginForm("DoRegister","Home")) {/*snipped*/} //in _Register.cshtml
Also I would change the Html.RenderAction calls to
Html.RenderPartial("_Login");
Html.RenderPartial("_Register");

Related

MVC 5 - How to avoid data being visible in the address bar on Form Submit

It may be a stupid question because when I googled I can't find anybody asking this question. But my requirement is to not to show any values in the querystring. The below url shows Name and Password values in clear text, but I don't want it to show.
http://localhost:30813/Home/Index/0?Name=test&Password=test
Model:
public class User
{
public int Id { get; set; }
public string Name { get; set; }
//public SecureString Password { get; set; }
public string Password { get; set; }
}
LoginController's view:
#using (Html.BeginForm())
{
<div>#Html.Label("Username:")</div>
<div>#Html.TextBoxFor(model => model.Name)</div>
<div>#Html.Label("Password:")</div>
<div>#Html.PasswordFor(model => model.Password)</div>
<div>
<input type="submit"
class="btn btn-primary"
value="Login"
style="line-height:normal!important;" />
</div>
}
LoginController.cs
[HttpGet]
public ActionResult Index()
{
return View();
}
[HttpPost]
public ActionResult Index(User user)
{
if (IsValidUser(user.Name, user.Password))
{
return RedirectToAction("Index", "Home", user);
}
return View();
}
You are not very clear on what you want to do. You are also unclear on what you Index action method looks like. So I am going to go on what I see in front of me.
When you do this..
return RedirectToAction("Index", "Home", user);
..you are redirecting to an HTTP GET action method named Index in the Home controller. It is unclear to me if this is an empty action method or an action method that needs a User instance.
If you do not need a User instance then you can just do this:
return RedirectToAction("Index", "Home");
If you do require a User instance and you do not want to pass the values as parameters in the URL, then I suggest you do something like this (it is not my ideal way of doing it):
[HttpPost]
public ActionResult Index(User user)
{
if (IsValidUser(user.Name, user.Password))
{
TempData["user"] = user;
return RedirectToAction("Index", "Home");
}
return View();
}
And then your Home controller's Index action method will look something like this:
public ActionResult Index()
{
User user = (User)TempData["user"];
return View();
}
Now you have an instance of your user variable and can be used accordingly.
I hope this helps.
As other pointed out, when you pass some data in GET requests, that is passed using querystring. So if you don't want to show the user specific data there, you cannot keep the user object in the RedirectToAction().
One way you can achieve such functionality is to use Session variables:
In LoginController:
if (IsValidUser(user.Name, user.Password))
{
Session.Add("user",user);
return RedirectToAction("Index", "Home");
}
return View();
And in HomeController,Index():
public ActionResult Index()
{
User user = (User)Session["user"];
Session.Remove("user");
//other logics here
return View();
}
An alternative which I use for more control of my user experience is to post my form data via ajax.
Its a bit more PT but it allows me to interact with the user more in that I can easily show a progress indicator or quickly report an error message:
#using (Html.BeginForm())
{
<div>#Html.Label("Username:")</div>
<div>#Html.TextBoxFor(model => model.Name)</div>
<div>#Html.Label("Password:")</div>
<div>#Html.PasswordFor(model => model.Password)</div>
<div>
<input type="button"
class="btn btn-primary"
value="Login"
style="line-height:normal!important;" />
</div>
}
<script>
$('form button').on('click', function() {
Login();
});
function Login() {
$('#progress').show(); // indicate work is being done
var Name = $('#Name').val();
var Password = $('#Password').val();
var User = { "Name": Name, "Password": Password };
$.post('/Home/Login', User, function(data) {
if (!data.IsOk) {
$('#progress').hide(); // work complete, stop showing progress
alert(data.Message); // show an error message from the controller
}
else {
location.href = '#Url.Content("~/")';
}
});
}
</script>
This is over-simplified. You can do a lot more. As in my case, I'm displaying MDL dialogs that have the error message and titles on them.
The controller looks something like this:
[HttpPost]
public JsonResult Login(UserViewModel user)
{
try
{
if (IsValid(user))
{
generateCookie(user);
return Json(new { IsOk = true }, JsonRequestBehavior.AllowGet);
}
else
{
return Json(new { IsOk = false, Message = "Invalid user credentials" }, JsonRequestBehavior.AllowGet);
}
}
catch (Exception ex)
{
return Json(new { IsOk = false, Message = ex.Message }, JsonRequestBehavior.AllowGet);
}
}
Once again, I've over-simplified this so as to give you the gist of it. The Controller handles all of the backend logic and database querying, and the View takes care of presentation.
If the user is valid, the Controller will return a true bool value to the View which will then redirect back to the home page which is catered to show dynamic content.
On a final note, its worth mentioning that MVC comes with built-in login functionality which is probably a lot more secure that writing our own. I'm not exactly sure how to use it though and I have little time available to figure it out but its something you might want to investigate.

What is the correct way of rendering a view?

I am trying to create an MVC 4 ASP.net site. As I am new to programming I would like to know what is the correct way of rendering a view based on if a user is logged in or not.
My Code: I am trying to restrict a user from going to the Index, About and Contact pages. It will only go to those pages(views) if the user is Logged In. My question is, "Is this the right way of doing it or is this wrong? Is there a more secure, effective, and acceptable way of doing this?"
Please let me know if there is. Thank You
public class HomeController : Controller
{
public ActionResult Index()
{
if (User.Identity.IsAuthenticated)
{
return View();
}
return RedirectToRoute(new { controller = "Account", action = "Login" });
}
public ActionResult About()
{
if (User.Identity.IsAuthenticated)
{
ViewBag.Message = "Your app description page.";
return View();
}
return RedirectToRoute(new { controller = "Account", action = "Login" });
}
public ActionResult Contact()
{
if (User.Identity.IsAuthenticated)
{
ViewBag.Message = "Your contact page.";
return View();
}
return RedirectToRoute(new { controller = "Account", action = "Login" });
}
The Authorize attribute lets you indicate that authorization is restricted to predefined roles or to individual users.
This gives you a high degree of control over who is authorized to view any page on the site.
If an unauthorized user tries to access a method that is marked with the Authorize attribute, the MVC framework returns a 401 HTTP status code.
If the site is configured to use ASP.NET forms authentication, the 401 status code causes the browser to redirect the user to the login page.
You can use [Authorize] on your controller if all the methods require login as below:
[Authorize]
public class HomeController : Controller
{
public ActionResult Index()
{
return View();
}
public ActionResult About()
{
ViewBag.Message = "Your app description page.";
return View();
}
public ActionResult Contact()
{
ViewBag.Message = "Your contact page.";
return View();
}
You can also put the attribute on certain methods if required instead of putting on the controller itself. For example, if you want the user to login for Index() method only then you could do it as below:
public class HomeController : Controller
{
[Authorize]
public ActionResult Index()
{ 
   return View();      
}
}
The common way for this case is usage of [Authorize] (AuthorizeAttribute)
You may add it to specific Actions or whole Controller. It either supports specific users restrictions and Roles as well.
You may start with default MVC solution from Visual Studio, which will create all basic functionality based on SimpleMembership provider.
You may refer to NerdDinner project for full explanation: http://nerddinnerbook.s3.amazonaws.com/Part9.htm.

RedirectToAction not return View

I am trying to redirect to action and get a new view (a new page) with no success. While debugging, I'm reaching the controller but not getting the view (the page URL is not changed).
With Fiddler I see that the page returns the right view result but in the browser the URL is not changed!
When shopping cart is empty, I would like to redirect to a new page a display the error message.
[HttpPost]
public RedirectToRouteResult PlaceOrder(DeliveryDetails deliveryDetails)
{
if (UserCart.IsEmpty)
{
TempData["errorMsg"] = "Error: Cart is empty";
return RedirectToAction("Index", "Error");
}
else
{
InsertOrder();
}
}
ErrorController:
public ActionResult Index()
{
return View();
}
ErrorController View:
#TempData["errorMsg"]
Thanks.
Then I would surmise that UserCart.IsEmpty is evaluating to false. What does your Error Index route look like? Also, you're better off returning a base ActionResult from a controller action in case you need to return a view. Presumably there's more code in the PlaceOrder method because that won't compile as it stands
Use ActionResult instead of RedirectToRouteResult
[HttpPost]
public ActionResult PlaceOrder(DeliveryDetails deliveryDetails)
{
// .....
return RedirectToAction("Index", "Error");
}

ASP.net MVC4 Clear Model Values

So I have a simple action in my controller. The project is a MVC Mobile Application.
public ActionResult Index()
{
return View();
}
this gives an form to enter data. I then handle the data in the post back.
[HttpPost]
public ActionResult Index(ScanViewModel model)
{
if (ModelState.IsValid)
{
Scan ns = new Scan();
ns.Location = model.Location;
ns.Quantity = model.Quantity;
ns.ScanCode = model.ScanCode;
ns.Scanner = User.Identity.Name;
ns.ScanTime = DateTime.Now;
_db.Scans.Add(ns);
_db.SaveChanges();
}
return View(model);
}
I want to clear the fields in the form and allow users to enter data again. However I get the exact same values back into my inputs. How can I clear them in the controller.
Just call this.ModelState.Clear()
You should follow the PRG pattern.
Just redirect to the Action method which is meant for the Create Screen. You can use the RedirectToAction method to do so.
RedirectToAction returns an HTTP 302 response to the browser, which causes the browser to make a GET request to the specified action.
[HttpPost]
public ActionResult Index(ScanViewModel model)
{
if(ModelState.IsValid)
{
//Code for save here
//..............
_db.SaveChanges();
return RedirectToAction("Index","User");
}
return View(model);
}
public ActionResult Index()
{
return View();
}
Assuming your controller name is UserController.

HTTP Post request from different controller actions and ModelState

I have a weird need in an ASP.NET MVC 3 application which blocks my current progress. Here is the case:
I have a little search engine for the products and I render this search engine on multiple pages. This SE makes a HTTP POST request to product controller's search action. It fine till here.
Let's assume that I am on home controller's index action (/home/index). I make a search and check if ModelState.IsValid. As a result, it is not valid. So, I should return this back with the entered model (so that user won't lose the values) and model state errors. But when I do that I ended up with different URL (/product/search) as expected.
If I do a redirect, I lose the ModelState and cannot display error messages.
I have different solutions so far and they all look dirty. Any idea?
Edit
Here is a little project which demonstrates this:
This is the ProductController:
public class ProductController : Controller {
[HttpPost]
public ActionResult Search(SearchModel searchModel) {
if (ModelState.IsValid) {
//Do some stuff...
return RedirectToAction("Index", "SearchResult");
}
return View(searchModel);
}
}
This is the SearchModel:
public class SearchModel {
[Required]
public string ProductCategory { get; set; }
[Required]
public string ProductName { get; set; }
}
This is the *_SearchPartial*:
#model MvcApplication20.SearchModel
#using (Html.BeginForm("search", "product"))
{
#Html.EditorForModel()
<input type="submit" value="Search" />
}
And finally this is the Home controller Index action view which renders the *_SearchPartial*:
#{
ViewBag.Title = "Home Page";
}
<h2>#ViewBag.Message</h2>
#Html.Partial("_SearchPartialView")
Here, when I submit the form and if the model state fails, how should I proceed at the Product controller Search action?
Here, when I submit the form and if the model state fails, how should
I proceed at the Product controller Search action?
Normally in this case you should render the _SearchPartialView but not as a partial but as a full view with layout so that the user can fix his errors. No need to stay at Home/Index in this case:
[HttpPost]
public ActionResult Search(SearchModel searchModel) {
if (ModelState.IsValid) {
//Do some stuff...
return RedirectToAction("Index", "SearchResult");
}
// since we are returning a view instead of a partial view,
// the _SearchPartialView template should be displayed with the layout
return View("_SearchPartialView", searchModel);
}
And if you wanted to stay on the same page upon error you could use an AJAX call to perform the search. So you would AJAXify this search form and then in the success callback test the result of the Search action and based on it decide whether to refresh the partial in order to show the error or redirect to the results action using window.location.href:
something along the lines of:
$(document).on('submit', '#searchForm', function() {
$.ajax({
url: this.action,
type: this.method,
data: $(this).serialize(),
success: function(result) {
if (result.redirectTo) {
// no validation errors we can redirect now:
window.location.href = result.redirectTo;
} else {
// there were validation errors, refresh the partial to show them
$('#searchContainer').html(result);
// if you want to enable client side validation
// with jquery unobtrusive validate for this search form
// don't forget to call the .parse method here
// since we are updating the DOM dynamically and we
// need to reattach client side validators to the new elements:
// $.validator.unobtrusive.parse(result);
}
}
});
return false;
});
This obviously assumes that you have now wrapped the partial call in a div with id="searchContainer" and that you provided an id="searchForm" when generating the search form:
<div id="searchContainer">
#Html.Partial("_SearchPartialView")
</div>
and now the search action:
[HttpPost]
public ActionResult Search(SearchModel searchModel) {
if (ModelState.IsValid) {
//Do some stuff...
return Json(new { redirectTo = Url.Action("Index", "SearchResult") });
}
return PartialView("_SearchPartialView", searchModel);
}
As far as I know the ModelState is lost when doing a RedirectToAction, the solution would be to save the modelstate in the TempData one example of this, that I'm using is this:
http://weblogs.asp.net/rashid/archive/2009/04/01/asp-net-mvc-best-practices-part-1.aspx#prg
This is also discussed in various posts for instance MVC Transfer Data Between Views

Resources