MVC Partial View Postback calling wrong method - razor

I have the following partial view with an Html.FormBegin helper method;
#if (Session["UserSpecific"]!=null) //(Request.IsAuthenticated)
{
using (Html.BeginForm("LogOff", "Account", FormMethod.Post, new { id = "logoutForm", #class = "navbar-right" }))
{
#Html.AntiForgeryToken()
#* #Html.ActionLink("Hello " + User.Identity.GetUserName() + "!", "Index", "Manage", routeValues: null, htmlAttributes: new { title = "Manage" })*#
<input type="button" class="menu-button nav navbar-nav navbar-right" data-target=".customer-list" data-toggle="collapse" />
<div class="customer-list-container">
<div class="customer-list collapse">
<ul>
#{
string[] array = { "Steel Company", "Recycling Company", "Other Company", "Random Name Here" };
}
#foreach (string name in array)
{
<li>#Html.ActionLink(name, "", "", routeValues: null, htmlAttributes: new { #class = "customer-link" })</li><hr/>
}
<li><input type="submit" /></li>
</ul>
<div class="col-xs-6">#Html.ActionLink("Settings", "Index", "Manage", routeValues: null, htmlAttributes: new { title = "Manage" })</div><div class="col-xs-6"><!--<input type="submit" value="Log Off"/>-><!--Log off</div><div class="clearfix">--></div>
</div>
</div>
}
}
The problem is, whenever I click on the Submit button, instead of logging out, it runs through the LogIn method again, and modifies the URL to read:
Account/Login?ReturnUrl=%2FAccount%2FLogOff
I don't get why it is running through the LogIn method again, since the Html.BeginForm method specifies that it will use the LogOff method in the Account controller.
The Login method that gets called:
// GET: /Account/Login
[AllowAnonymous]
public ActionResult Login(string returnUrl)
{
ViewBag.ReturnUrl = returnUrl;
return View();
}
The LogOff method that should be called:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult LogOff()
{
//AuthenticationManager.SignOut();
Session.Remove("UserSpecific");
Session["UserSpecific"] = null;
return RedirectToAction("Index", "Home");
}

It looks like the Index action method on your HomeController is not marked with the [AllowAnonymous] attribute, which means when the browser attempts to access it after being logged out it gets redirected to the login page again because you're trying to access a page that is accessible only to authenticated users.
Try adding [AllowAnonymous]:
public class HomeController : Controller {
[AllowAnonymous] // <-- add this
public ActionResult Index() {
return View();
}
// other stuff
}

I just realised something interesting, I was facing the same problem with my MVC area calling the index method in the account controller instead of calling the logoff method.
I realised the problem was with a defective routing done in the admin area of the application.
Try testing that to see if you're able to correct it.

Related

MVC Dropdown List containing Method Actions for Models, Possible?

I have several models in my application which I want to create functionality to allow users to edit/create values within the database.
Of course each controller contains the action method, but I want to be able to provide the user with a dropdown that lists all of the models so when they select an option from the dropdown it takes the user to the correct view to Edit or Ceate an item in that model.
I.e. I have models for GoverningBody, Directorate, Region, and OperationalTeam, each of them have the following elements;
.....Id (int),
.....Name (string),
Live (bit)
(Live is used as a method for soft deleting of the value in order to protect historic data) I want to have a dropdown with these listed, the user selects one from the dropdown, clicks a button, and the user is then provided the Edit view, or Create view for that selected model.
I've done a but of research on the internet but cannot find any kind of solution nor anything that explains if what I'm attempting to achieve is even possible, and its most likely down to the fact that I don't know enough to know what I should be looking for.
I'm not asking for anyone to provide me with a solution, but any advice on what/where I should be looking, what terms to look for and learn about so I can attempt something on my own.
Any advice would be most appreciated.
Thanks
In my opinion, You can create two DropdownList, One is choose model and the other is choose action, I create a simple demo to show my opinion, Hope it can help you.
First, I create two models for this demo (User, GoverningBody), Then I Use Model Name to create controller with CURD action
public class UserController : Controller
{
public IActionResult Create()
{
return View();
}
public IActionResult Edit()
{
return View();
}
public IActionResult Update()
{
return View();
}
public IActionResult Delete()
{
return View();
}
}
public class GoverningBodyController : Controller
{
public IActionResult Create()
{
return View();
}
public IActionResult Edit()
{
return View();
}
public IActionResult Update()
{
return View();
}
public IActionResult Delete()
{
return View();
}
}
HomeController
public IActionResult Index()
{
List<SelectListItem> model = new List<SelectListItem>();
model.Add(new SelectListItem { Text = "User", Value = "User" });
model.Add(new SelectListItem { Text = "GoverningBody", Value = "GoverningBody" });
ViewBag.model = model;
List<SelectListItem> action = new List<SelectListItem>();
action.Add(new SelectListItem { Text = "Create", Value = "Create" });
action.Add(new SelectListItem { Text = "Edit", Value = "Edit" });
action.Add(new SelectListItem { Text = "Update", Value = "Update" });
action.Add(new SelectListItem { Text = "Delete", Value = "Delete" });
ViewBag.action = action;
return View();
}
View
<h2>Choose a model and an action you want to do with this model</h2>
<div>
Model: <select asp-items="#ViewBag.model" name="model" id="model"></select>
Action : <select asp-items="#ViewBag.action" name="action" id="action"></select>
</div>
<button type="button" onclick=go()>Go~</button>
#section Scripts{
<script>
function go(){
var controller = document.getElementById("model").value;
var action = document.getElementById("action").value;
window.location.href= "/"+controller+"/"+action;
}
</script>
}
Demo:

MVC5 Post data from partial view being used in main view

I have already tried many solutions available on web as per my understanding in context to this question but being new in MVC I am still unable to find a solution. Kindly help.
I have a view which is the home page of the website named as "Index.cshtml" and is situated under the following path:
WebsiteName/Areas/Website/Views/CypressHome/Index.cshtml
Secondly, I have a created a user trial form as partial view named as "_PartialHomeFormFreeTrial.cshtml" which is situated under the following path:
WebsiteName/Areas/Website/Shared/_PartialHomeFormFreeTrial.cshtml.
This form I have used inside my "Index.cshtml" as below:
<!--freetrialform-->
#Html.Partial("_PartialHomeFormFreeTrial")
<!--//freetrialform-->
Now, my partial page is posting data comprising of following elements:
#using (Html.BeginForm("Create", "Customer", FormMethod.Post))
{
#Html.AntiForgeryToken()
<div>
#Html.EditorFor(model => model.CustomerName, new { htmlAttributes = new { #class = "input__field input__field--kuro" } })
#Html.ValidationMessageFor(model => model.CustomerName, "", new { #class = "text-danger" })
............
other fields such as email, phone, date, etc..
<input type="submit" id="" value="SEND REQUEST" />
</div>
}
Now, I have created a controller named "CustomerController" which has the following code to save the data of the form as used as partial view in the main view "Index.cshtml":
public class CustomerController : Controller
{
private WebsiteContext db = new WebsiteContext();
// GET: Website/Customer
public ActionResult Index()
{
return View();
}
// GET: Website/Customer/Create
public ActionResult Create()
{
ViewBag.StatusPlanID = new SelectList(db.StatusPlans, "StatusPlanID", "Status");
return View("Index");
}
// POST: Website/Customer/Create
[HttpPost]
public ActionResult Create([Bind(Include = "CustomerID,CustomerName,CustomerEmail,CustomerPhone,DateOfRegistration,StatusPlanID")] Customer customer)
{
if (ModelState.IsValid)
{
db.Customers.Add(customer);
db.SaveChanges();
return RedirectToAction("Index");
}
ViewBag.StatusPlanID = new SelectList(db.StatusPlans, "StatusPlanID", "Status", customer.StatusPlanID);
return View(customer);
}
}
I have tried many changes in my controller, return in the views and
many other things but I am getting the same error always. Neither the
validations are working nor the validated data is getting saved.
The error is as below:
Server Error in '/' Application.
The view 'Create' or its master was not found or no view engine supports the searched locations. The following locations were searched:
~/Areas/Website/Views/Customer/Create.aspx
~/Areas/Website/Views/Customer/Create.ascx
~/Areas/Website/Views/Shared/Create.aspx
~/Areas/Website/Views/Shared/Create.ascx
~/Views/Customer/Create.aspx
~/Views/Customer/Create.ascx
~/Views/Shared/Create.aspx
~/Views/Shared/Create.ascx
~/Areas/Website/Views/Customer/Create.cshtml
~/Areas/Website/Views/Customer/Create.vbhtml
~/Areas/Website/Views/Shared/Create.cshtml
~/Areas/Website/Views/Shared/Create.vbhtml
~/Views/Customer/Create.cshtml
~/Views/Customer/Create.vbhtml
~/Views/Shared/Create.cshtml
~/Views/Shared/Create.vbhtml
And the url is changing as below:
1. On running the system initially: http://localhost:53872/
2. On clicking on submit: http://localhost:53872/Areas/Website/Customer/Create along with the
error as stated above.
For more information my WebsiteAreaRegistration.cs file contains the below code:
public class WebsiteAreaRegistration : AreaRegistration
{
public override string AreaName
{
get
{
return "Website";
}
}
public override void RegisterArea(AreaRegistrationContext context)
{
context.MapRoute(
"Website_home",
"",
new { controller = "CypressHome", action = "Index", id = UrlParameter.Optional }
);
context.MapRoute(
"Website_default",
"Areas/Website/{controller}/{action}/{id}",
new { controller = "CypressHome", action = "Index", id = UrlParameter.Optional }
);
}
}
Though I have understood the problem but unable to figure out. Kindly help.
In your code last return statement is return View(customer). That's means after POST data it return a View (using HTTP GET method) as same name as Action that's Create. But your description you have a Create Action but you have no view page.
So please create a Create.cshtml with a Model corresponding customer Object.
Or change the return statement.
Based on your comment you can follow this Change.
1.Remove
public ActionResult Create()
{
ViewBag.StatusPlanID = new SelectList(db.StatusPlans, "StatusPlanID", "Status");
return View("Index");
}
2.then change
public ActionResult Index()
{
ViewBag.StatusPlanID = new SelectList(db.StatusPlans, "StatusPlanID", "Status");
return View(new Customer());
}
3.in Index.cshtml
#Model Customer
#Html.Partial("_PartialHomeFormFreeTrial",Model)
4.Then
return View("Index",customer);

ASP.NET Cross session list

I'm attempting to make a simple page that will compare multiple form submissions.
I have a html page with a form, and a for-loop that generates a div for each item in a list of form submissions. The list is passed from the controller. I am trying to maintain the list in the controller rather than rely on a database.
When I try to resubmit the form, which should add another object to the list, the list re initializes.
In debugging, I see that the list is empty when the form gets submitted. I'm unsure as to the correct terminology, but it seems that the list is emptied whenever the view is rendered. Is there a way to maintain list contents?
I know there are better ways to do this, and welcome any advice. I'm still learning, so pleas go easy.
Thanks!
This is the simplified controller.
namespace MvcApplication2.Controllers
{
public class HomeController : Controller
{
List<paymentPlan> plansList = new List<paymentPlan>();
public ActionResult Index()
{
return View(plansList);
}
[HttpPost]
public ActionResult Index(FormCollection collection)
{
paymentPlan Project = new paymentPlan();
Project.customerName = Convert.ToString(collection["customerName"]);
plansList.Add(Project);
return View(plansList);
}
}
}
This is my simplified view.
#model List<MvcApplication2.Models.paymentPlan>
#using (Html.BeginForm("index", "home", FormMethod.Post, new { Id = "signupForm" }))
{
<label for="customerName">Customer Name:</label>
<input type="text" name="customerName" class="form-control required" />
#Html.ValidationSummary(true)
<input type="submit" value="Calculate" class="btn btn-primary" />
}
#{
bool isEmpty = !Model.Any();
if (!isEmpty)
{
foreach (var i in Model)
{
<div>
Name: #i.customerName
</div>
}
}
}
This is my simplified model.
namespace MvcApplication2.Models
{
public class paymentPlan
{
public string customerName { get; set; }
}
}
I think that's a question of controller and asp.Net MVC lifecycle !
A controller lifetime is the same as the request, for each request a new controller is created and once the work is done it's disposed!
So try to remove this List<paymentPlan> plansList = new List<paymentPlan>(); and work with TempData[] or ViewData[] or Session[] like this :
Controller
public class HomeController : Controller
{
public ActionResult Index()
{
Session["plansList"] = ((List<paymentPlan>)Session["plansList"])!=null? (List<paymentPlan>)Session["plansList"] : new List<paymentPlan>();
return View((List<paymentPlan>)Session["plansList"]);
}
[HttpPost]
public ActionResult Index(FormCollection collection)
{
paymentPlan Project = new paymentPlan();
Project.customerName = Convert.ToString(collection["customerName"]);
((List<paymentPlan>)Session["plansList"]).Add(Project);
return View(plansList);
}
}
check this : http://www.asp.net/mvc/overview/getting-started/lifecycle-of-an-aspnet-mvc-5-application

How to pass value of Html.Dropdownlist to Controller

I am dynamically generating a dropdownbox.
I am trying to send the selected value in dropdown box as one of the fields of a model to controller.
#using (Html.BeginForm("AddItem", "Home", FormMethod.Post, new { enctype = "multipart/form-data" }))
{
<label>
Category:</label>
#Html.DropDownList("Category", (IEnumerable<SelectListItem>)ViewData["CategoryList"])<br />
<label>
Sku:</label>
#Html.TextBox("newItem.Sku")<br />
<label>
Title:</label>
#Html.TextBox("newItem.Title")<br />
I am able to send all the values as a part of model, except the value of Category(dropdownbox value), hence making the function in controller to fail..
ANSWER: Renaming the Dropdownlist "Category" to = "newItem.Category", did the work, basically it should match the model name.
Create a ViewModel for your Item with Properties to hold all Categories and SelectedCategoryId value
public class ItemViewModel
{
public int ItemId { set;get;}
public string SKU { set;get;}
public string SelectedCategoryId { get; set; }
public IEnumerable<SelectListItem> Categories{ get; set; }
}
In your home controller, Create Action method for Add where you create an object of ItemViewModel, Set the Categories and return to the View which is strongly typed.
public ActionResult AddItem()
{
ItemViewModel objNewItem=new ItemViewModel();
objNewItem.Items = new[]
{
new SelectListItem { Value = "1", Text = "Perfume" },
new SelectListItem { Value = "3", Text = "Shoe" },
new SelectListItem { Value = "3", Text = "Shirt" }
};
return View(objNewItem);
}
The Strongly typed View
#model ItemViewModel
#using (Html.BeginForm("AddItem", "Home", FormMethod.Post, new { enctype = "multipart/form-data" }))
{
Category:
#Html.DropDownListFor(x => x.SelectedCategoryId ,new SelectList(Model.Categories,"Value",Text"), "Select..")
<input type="submit" value="Add" />
}
And have the HTTPPOST AddItem method in your Home Controller
[HttpPost]
public ActionResult AddItem(ItemViewModel objItem)
{
//Now you can access objItem.SelectedCategoryId and do what you like to do...
}
Your DropDown is bound to a field called Category. So you must have such field on your view model.
public class MyViewModel
{
public string Category { get; set; }
...
}
and your action:
[HttpPost]
public ActionResult AddItem(MyViewModel model)
{
// model.Category will contain the selected value
...
}
Also note that this property must be a simple scalar value (string, int, ...) and not a complex object.
And once you have an adapted view model you could use the strongly typed versions of the helpers:
#model MyViewModel
#using (Html.BeginForm("AddItem", "Home", FormMethod.Post, new { enctype = "multipart/form-data" }))
{
#Html.LabelFor(x => x.Category)
#Html.DropDownListFor(x => x.Category, Model.Categories)
<br />
#Html.EditorFor(x => x.Sku)
<br/>
#Html.EditorFor(x => x.Title)
...
}
Renaming the Dropdownlist "Category" to = "newItem.Category", did the work, basically if you expect a model to be received in controller, which in this case was "newItem", down control name should match the model name and its property.

Admin login user controller

I'm trying to implement a simple login for admin in a MVC3 project. I'm new to ASP.NET and to MVC3. I have googled and asked questions! have seen a lot of really nice ways of implementing this but they were all on a very abstract level and quite frankly maybe a bit to high for me at the time being. I have the credentials in my db so basically I just want to query that one and redirect the user if login matches those and if not show login form again. So this is what I got. My model:
public class FormModel
{
public bool isAdmin { get; set; }
[Required(ErrorMessage = "Please enter your Username")]
//[Remote("ValidateUserName", "Login", ErrorMessage = "UserName already taken!")]
[StringLength(6, MinimumLength = 3)]
[Display(Name = "Username:")]
[RegularExpression(#"(\S)+", ErrorMessage = "White space is not allowed")]
public string UserName { get; set; }
[Required(ErrorMessage = "Please enter your Password")]
[DataType(DataType.Password)]
[Display(Name = "Password:")]
public string Password { get; set; }
}
public User IsAdmin(string username, string password)
{
return (from user in db.Users
where user.username == username && user.password == password <--- alternative here is to just match username and pass against the data I have in my db columns(testdata 'admin', 'password')
&& user.IsAdmin == true
select user).SingleOrDefault();
}
And in my controller basically this right now:
public ActionResult Index()
{
//some code here maybe a conditional
return View();
}
And finally my view:
#model Web.VoucherCannon.Models.FormModel
#using (Html.BeginForm("HandleForm", "Login", FormMethod.Post, new {id = "myForm"})) {
#Html.ValidationSummary(true)
<div class="editor-label">
#Html.LabelFor(model => model.UserName)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.UserName)
#Html.ValidationMessageFor(model => model.UserName)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.Password)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Password)
#Html.ValidationMessageFor(model => model.Password)
</div>
<button class="button">Log In</button>
}
So now. How do I use the returned result of the query in my controller so that I can login? I'm sure I will refactor this later on and make it with a dbcontext layer and so on, but just for now I will be happy to make this work. Grateful for help!
You should have 2 controller actions: one for rendering the login (accessible on GET) form and one for handling the submission (accessible on POST) and performing the actual authentication.
// This will render the Login view (the one you have shown)
public ActionResult Login()
{
var model = new FormModel();
return View(model);
}
// This one is responsible for handling the submission and credential verification
[HttpPost]
public ActionResult Login(FormModel model)
{
if (!ModelState.IsValid)
{
// The user submit the form but validation
// (as defined on the model using DataAnnotation attributes) failed
// => redisplay the view so that the user can fix his errors
return View(model);
}
// notice that you don't need to pass parameters to the IsAdmin method
// as it already contains the username and password as properties
if (!model.IsAdmin())
{
// The IsAdmin method didn't verify the credentials => add a model error
// and redisplay the login view
ModelState.AddModelError("username", "dude you are not an admin");
return View(model);
}
// OK, at this stage everything is fine => we can grant access
// by issuing an authentication cookie
FormsAuthentication.SetAuthCookie(model.UserName, false);
// finally we redirect to some home page for admins
return RedirectToAction("Index", "Admin");
}