I'm trying to call an ActionResult method from a web page form but I can't seem get the two items to connect.
The desired result is for the page to refresh and filter the model to display the required results.
Method in serversController.cs
The internal code works as intended when placed in ActionResult Index
[HttpPost]
public ActionResult activeServers()
{
// load the servers into local variable
var servers = from s in db.Servers
select s;
// filter out the archived servers
servers = servers.Where(s => s.archive.Equals(0));
return View(servers.ToList());
}
Button making the call
#using (Html.BeginForm())
{
<button name="activeServers" type="submit" value="activeServers" class="btn btn-default">Active</button>
}
Thanks is advance
Try to specify the action method, controller name (without the controller suffix) and the http method (it defaults to GET) in the BeginForm:
#using (Html.BeginForm("activeServers", "Servers", FormMethod.POST))
{
<button name="activeServers" type="submit" value="activeServers" class="btn btn-default">Active</button>
}
Related
I am asking a simple login page to get the user to enter a pin on a mobile responsive app its in house not online.
When the page is posted the user clicks the submit button the SaveUsers is the function that is called first this is on the Login Controller.
[HttpPost]
[ActionName("Index")]
public ActionResult SaveUsers(Users model)
{
BrianScott_SOMEntities usr = new BrianScott_SOMEntities();
var s = usr.GetUsers(model.Pin);
var item = s.FirstOrDefault();
if (item == "Success")
{
var sageDetails = usr.Users.Where(w => w.Pin ==model.Pin).FirstOrDefault();
HttpCookie cookie = new HttpCookie("BScotSalesOrderManagerLogin");
cookie.Values.Add("SageUserName", sageDetails.SageUserName.ToString());
cookie.Values.Add("SagePassword", sageDetails.SagePassWord.ToString());
cookie.Expires = DateTime.Now.AddDays(30);
Response.Cookies.Add(cookie);
return View("~/Views/Home/Index.cshtml");
}
else if (item == "User Does not Exists")
{
ViewBag.NotValidUser = item;
}
else
{
ViewBag.Failedcount = item;
}
return View("Index.cshtml");
}
The below form is the form that is represented with the above controller which prompts the user for their pin number.
#{
Layout = "~/Views/Shared/_Layout.cshtml";
}
#model Web.SOM.Models.Users
#using (Html.BeginForm())
{
<div class="bs-example" style="border:2px solid gray;">
<label>#ViewBag.SageUserName</label>
<div class="form-group centerlook">
<h1> Login </h1>
</div>
<div class="form-group centerlook">
<label>Pin: </label>
#Html.EditorFor(model => model.Pin )*
#Html.ValidationMessageFor(model => model.Pin)
</div>
<div class="form-group error">
#if (#ViewBag.Failedcount != null)
{
<label> Failed Attempt count is: #ViewBag.Failedcount</label>
}
#if (#ViewBag.NotValidUser != null)
{
<label> #ViewBag.NotValidUser</label>
}
</div>
<div class="loginbtn">
<input type="submit" value="Login" class="btn btn-primary" />
</div>
</div>
}
The home controller is what the controller in the first piece of codes redirects to it is the index action to which I want to hit.
public ActionResult Index()
{
if (Request.Cookies["BScotSalesOrderManagerLogin"] != null)
{
ViewBag.SageUserName = Request.Cookies["BScotSalesOrderManagerLogin"].Values["SageUserName"];
ViewBag.SagePassword = Request.Cookies["BScotSalesOrderManagerLogin"].Values["SagePassword"];
}
return View();
}
But the method is not getting hit unless I do a hard reload is there another way of moving to the other view and making sure that the index method is being hit ?.
Because when I look at the viewbag the items are null when they should contain a username and a password of the related row when I debug on the first page the values are their but then lost on the other page.
On my page i am wanting to display the information I am just doing
<label>Sage Username : #ViewBag.SageUserName </label>
But the value is blank? I am coming from a web forms background so please excuse me better late than never jumping ship
Your sequence of events is...
User POSTs to SaveUsers on Login (aliased as the action Index)
You return a view
User POSTs from that view to Index, still on Login
When you do this:
return View("~/Views/Home/Index.cshtml");
You're essentially overriding the framework's standard behavior. You're returning a specific file to be used as the view, but not telling the framework to switch any context or change the URL in any way. So from the browser's perspective, even though you returned a view from your server-side Home folder, the page is still /Login/Index and any form actions, links, etc. will be from there.
In general you should prefer redirects to manually specifying views. So a sensible sequence of events might be:
User POSTs to SaveUsers on Login (aliased as the action Index)
Server-side code performs its logic, redirects the user to Index on Home
User GETs Index on Home
Server returns the view
User POSTs to Index on Home
So your SaveUsers action method, when successful, can do something like:
return RedirectToAction("Index", "Home");
This will then cause the user to make a GET request to Index on Home, which can just return View() and that will default to the view you're manually returning now. Now from the browser's perspective it's on /Home/Index and all form actions, links, etc. will be from that context.
Additionally, if you always want a given form, link, etc. to point to a specific controller action regardless of where it was loaded from, you can specify that. For example, when you do this:
using (Html.BeginForm())
You are telling the framework that this form will "POST to the current URL, whatever that URL is". But when you do this:
using (Html.BeginForm("Index", "Home"))
This tells the framework that the form will always post to the Index action on the Home controller, regardless of the current URL.
can you try using
#using (Html.BeginForm(yourActionName, yourControllerName))
{
}
And use RedirectToAction to return to your Home Index..
I have two Create methods one decorated with HttpGet, and the other with HttpPost. I have a create view for the first one looking like this :
#{
ViewBag.Title = "Create";
}
<h2>Create</h2>
<form action="/" method="post">
<input type="text" name="txt" value="" />
<input type="submit" />
</form>
The methods :
List<string> myList = new List<string> { "element1", "element2", "element3" };
public ActionResult Create()
{
return View();
}
[HttpPost]
public ActionResult Create(string txt)
{
//myList.Add(Request.Form["txt"]);
myList.Add(txt);
return View();
}
I am simly trying to pass the data from my form on button to my second Create() and save it to myList.
I need some advice on how to make this work.
Once you've fixed your form (in that you're posting back to your application's default route (by default HomeController.Index() method) by sending the request to /, instead of your Create method), you are actually correctly adding the value to your list. The problem is, that value only stays for the current request.
To make things persistent, you need to consider a persistence layer in memory, in database, or in session. I've provided a full sample below that uses the session, which will give you a per-user list instance. Without this layer, your Controller is being routinely disposed of once the action has completed processing, and so the amends to your list are not persisted. This is the normal request lifecycle in ASP.NET and makes sense when you consider that your app is basically only ever dealing with 1 request at a time. It's important to note that making something static isn't a form of persistence per-se, in that its lifetime and reliability is indeterminable. It will appear to work, but once your application pool recycles (ie. the app is destroyed and reloaded in memory) you will have again lost all amends to your list.
I would suggest you read up on Session State to understand exactly what is going on below. In a nutshell, each application user / unique visitor to your site will be given a unique 'session ID', you can then use this session ID to store data that you wish to use on the server side. This is why, if you were to visit your Create method from separate browsers (or try Private mode) you will be maintaining two separate lists of data.
View (which also outputs the list to the user):
#model List<string>
#{
ViewBag.Title = "Create";
}
<h2>Create</h2>
<ul>
#foreach(var str in Model)
{
<li>#str</li>
}
</ul>
#using (Html.BeginForm())
{
<input type="text" name="txt" />
<input type="submit" />
}
Controller contents:
public List<string> MyList
{
get
{
return (List<string>)(
// Return list if it already exists in the session
Session[nameof(MyList)] ??
// Or create it with the default values
(Session[nameof(MyList)] = new List<string> { "element1", "element2", "element3" }));
}
set
{
Session[nameof(MyList)] = value;
}
}
public ActionResult Create()
{
return View(MyList);
}
[HttpPost]
public ActionResult Create(string txt)
{
MyList.Add(txt);
return View(MyList);
}
Please use this:
#using (Html.BeginForm("Create", "Controller", FormMethod.Post)){
<input type="text" name="txt" value="" />
<input type="submit" />
}
Replace Controller with your Controller name.
Or simply use:
#using (Html.BeginForm()){
<input type="text" name="txt" value="" />
<input type="submit" />
}
When you call BeginForm() without any parameters it default to using the same controller/action used to render the current page.
What I want to do
I am very new to MVC.
I'm trying to create a page that allows users to perform the following actions on the same page:
View the list (table)
Add a new item (Filling the form and clicking the Add button should update the table)
Delete an item from the list (Clicking the Delete button in a row should update the table)
A simple example looks like this but I actually have two lists on one page (Fees and Costs):
Question
What would be the best way to achieve this?
Should I go with Dylan Beattie's method posted here which would look something like this?
public ActionResult MyAction(string submitButton, MyViewModel form)
{
switch (submitButton)
{
case "AddFee":
return (AddFee(form));
case "AddCost":
return (AddCost(form));
case "RemoveFee":
return (RemoveFee(form));
case "RemoveCost":
return (RemoveCost(form));
}
}
public ActionResult AddFee(MyViewModel form)
{
Fee newFee = ....; // Get entered data from `form`
_repository.InsertFee(newFee);
return View("Create"); //Back to the original page
}
Or is there any other recommended methods to handle this such as using JavaScript?
You could create the table as a partial view and re render this via ajax.
Wrap the partial view in a div and Wrap the form in #using (Ajax.BeginForm(.... and target the wrapper div. Your controller action that is targeted by the ajax request will need to return a partial view.
Here is a simple example
public class HomeController : Controller
{
public ActionResult Index()
{
MYvm vm = new MYvm() { id = 1, name = "This is my View Model" };
return View(vm);
}
public ActionResult DA(MYvm vm)
{
vm.name = "CHANGED";
return PartialView("Part", vm);
}
View:
#model MvcApplication1.Controllers.HomeController.MYvm
#{
ViewBag.Title = "Home Page";
}
#using (Ajax.BeginForm("DA", "Home", new AjaxOptions() { UpdateTargetId = "cont", HttpMethod = "Get" }))
{
<div>
Id: #Html.EditorFor(model => model.id)
</div>
<div>
Name: #Html.EditorFor(model => model.name)
</div>
<input type="submit" value="SubmitForm" />
}
<div id="cont">
#{Html.RenderPartial("part", Model);}
</div>
Partial View
#model MvcApplication1.Controllers.HomeController.MYvm
#{
ViewBag.Title = "part";
}
<h2>part</h2>
#Model.name
Should I go with [previous SO answer]
No. That answer was for a different scenario where the question had a form with two submit buttons that wanted to do two different actions (and wasn't even the accepted answer to that question).
Your sample screenshot indicates that some javascript/jquery and ajax would solve the issue cleanly.
As you're new to MVC, try to keep it relatively simple. Break up the page into separate parts:
the containing page
the edit form
the list with remove
the edit/list work independently and should be written in a way that they could be put on any other page - the page is just there to contain them and doesn't do much else (obviously your real page will contain more, but add those parts as separate components as well).
1 Create actions for your list and edit forms that return partialviews - just the parts that are needed for that view (self-contained)
controller:
[HttpGet]
public ActionResult AddCost()
{
var model = new Cost();
return PartialView(model);
}
[HttpPost]
public void AddCost(Cost model)
{
if (ModelState.IsValid) {
db.SaveCost(model);...
}
}
form Views/Home/AddCost.cshtml:
#using (Ajax.BeginForm(...
{
<div class='editor-label'>#Html.LabelFor(model=>model.Description)</div>
...etc...
}
I'll leave you to set the Ajax.BeginForm properties. But make sure the on-success calls reloadCostList() (see below)
controller
public ActionResult CostList()
{
var model = db.loadCosts(); ...
return PartialView(model);
}
list, Views/Home/CostList.cshtml
#model IEnumerable<ViewModels.Cost>
<table>
<thead>
<tr>
<th>Cost Description</th>
...
<tbody>
#foreach (var cost in Model.Costs)
{
<tr data-id='#cost.Id'>
<td>#Html.DisplayFor(x=>cost.Description)</td>
...
<td><a href='#' class='remove-button'>Remove</a></td>
}
...
2 Create an action + view for the main page with placeholder for the form and calls the list partial-action, eg:
<div id="body">
<div id="formWrapper">
#Html.Action("AddCost")
</div>
<div id="listWrapper">
#Html.Action("ListView")
</div>
</div>
if you already load the data for the page, you can pass it directly to the partial, but there's no need:
#Html.Partial("ListView", Model.Costs)
this allows you to refresh the list via an ajax call, something like:
function reloadCostList() {
$(".listWrapper").load("Home/CostList");
}
(ideally, $.ajax and add some fancy UI to indicate loading)
3 Add a remove action to your controller
[HttpPost]
public void RemoveCost(int id)
{
}
4 Wire up the Remove link
$(function() {
$(".remove-button").click(function() {
var id = $(this).closest("tr").attr("id");
$.post("/Home/RemoveCost/" + id, null, function() {
$(".listWrapper").load("Home/CostList");
// or reloadCostList(); from above
// or:
//$(".listWrapper tr[id=" + id + "]").hide();
});
});
}
rather than re-load the entire list, you could just remove the row (add some fancy UI like fade-out...)
I have to allow the user to move to the next or previous form, I just need to save the model on navigation. Is there another way to pass back the model to the controller besides using submit? Since I need to redirect to other possible pages.
You could put your model object in the TempData collection on submit, redirect, then read it back out again. For example:
[HttpPost]
public ActionResult FirstForm(FirstFormModel model) {
TempData["TempModelStorage"] = model;
return RedirectToAction("SecondForm");
}
public ActionResult SecondForm() {
var firstModel = TempData["TempModelStorage"] as FirstFormModel;
// check for null, use as appropriate, etc.
return View(...);
}
More details here: http://msdn.microsoft.com/en-us/library/dd394711(v=vs.100).aspx
You may save the data asynchronously using jQuery ajax on those button click events.
Assuming your View is something like this
#using(Html.BeginForm("Save","Items"))
{
<div>
Name : #Html.TextBoxFor(s=>s.Name)
<input type="button" class="navigBtns" value="Prev" />
<input type="button" class="navigBtns" value="Next" />
</div>
}
And your script is
$(function(){
$(document).on("click",".navigBtns",function() {
e.preventDefault();
var _this=$(this);
$.post(_this.closest("form").attr("action"), _this.closest("form").serialize(),
function(res){
//check res variable value and do something as needed
// (may be redirect to another page /show/hide some widgets)
});
});
});
Assuming you have an action method called Save in your controller to handle the saving part.
Was given a neat article about this.
MVC Wizard Example
Basically this, you literally pass the name of the html button.
In the view form
<input type="submit" name="btnPrev" />
<input type="submit" name="btnNext" />
In the Controller
Controller
public ActionResult DoStuff(ModelClass mc,string btnPrev,string btnNext)
{
string actionString = "previousPage";
if(btnNext != null)
actionString = "nextPage";
return RedirectToAction(actionString,"Controller")
}
I have a search box in a Razor template:
#{
using (Html.BeginForm("Detail", "Book", FormMethod.Get))
{
#Html.TextBox("Id")
<input type="submit" value="Search" />
}
}
When I submit a search it goes to a url like:
~/Book/Detail?Id=1234
However I want it to format the url like so, just because I think it looks cleaner:
~/Book/Detail/1234
Which works perfectly fine because the controller method signature looks like this:
// GET: /Book/Detail/id
public ActionResult Detail(string id)
Model with TextBoxFor
I've tried a Html.TextBoxFor:
#model WebApplication.Models.SearchModel
#{
using (Html.BeginForm("Detail", "Book", FormMethod.Get))
{
#Html.TextBoxFor(m => m.Id)
<input type="submit" value="Search" />
}
}
Same result.
I think you want to take a look at the #Html.BeginRouteForm method, like in this question.
You use a GET request. This means that all parameters will appear in the url box.
I can't check now, but I suppose you could use these options:
The IIS url rewrite - http://www.iis.net/downloads/microsoft/url-rewrite
Url rewrite through a web.config - http://www.hanselman.com/blog/RedirectingASPNETLegacyURLsToExtensionlessWithTheIISRewriteModule.aspx
And a batch of stupid methods:
You can change your request to POST and then modificate the Url by the JS - Modify the URL without reloading the page
You can redirect the request
Also, did you try to add a personal routing for the search url?
Try using a model for the form submit and use #Html.TextBoxFor.
The answer was to add a new search action then redirect to the detail. This is nice because I can choose to do more when searching, such as returning a different view if the query has multiple matches.
//
// GET: /Book/Search?query=
public ActionResult Search(string query)
{
return RedirectToAction("Detail", new { id = query });
}
//
// GET: /Book/Detail/id
public ActionResult Detail(string id)
Razor:
#{
using (Html.BeginForm("Search", "Book", FormMethod.Get))
{
#Html.TextBox("query")
<input type="submit" value="Search" />
}
}