accessing controller method on button click - c#

I am new to C# and MVC, while I understand the Controller and the Model side. I have encountered a problem when accessing methods within a controller in order to do a simple conversion that I can then return to my View.
My Controller:
public class Exercise05Controller : Controller
{
//
// GET: /Exercise05/
public ViewResult Index()
{
return View();
}
public ActionResult GramsToOunces(double? grams)
{
ViewData["grams"] = grams;
ViewData["ounces"] = (grams * 0.035d);
if (grams < 5)
{
return RedirectToAction("Index", "Home");
}
else if (grams > 5)
{
return View("GramsToOunces");
}
return RedirectToRoute(new
{
controller = "Exercise05",
action = "Index"
});
}
}
My Index View:
#{
ViewBag.Title = "Index";
}
<h2>Index</h2>
<form action="/Exercise05/GramsToOunces" method="post">
<input name="grams" type="text" placeholder="Insert grams to convert to ounces" />
<input type="submit" value="Convert Grams to Ounces" />
</form>
My GramsToOunces View:
#{
ViewBag.Title = "Index";
}
<h2>GramsToOunces</h2>
<!-- Currently nothing else here -->
I believe my issue is arising somewhere on this line; action="/Exercise05/GramsToOunces". Using debugging has shown me that the the controller processes all the information and gets ready to return the view to suddenly just not return anything. I wish to do a simple conversion and then return this as a view. Would someone be able to point me as to where I am going wrong?

As long as your view is named the same as the controller action, try:
#using (Html.BeginForm())
{
<input name="grams" type="text" placeholder="Insert grams to convert to ounces" />
<input type="submit" value="Convert Grams to Ounces" />
}

This is just a sanity check, but you are using the line return View("GramsToOunces", "ounces");. According to the docs http://msdn.microsoft.com/en-us/library/dd470743(v=vs.118).aspx, first string is the view name and second string is the master page. Do you have a master page called "ounces"? If not that could explain why no result is returning. Perhaps you want to use return View("GramsToOunces");

If you simply want to do the conversion and show the result to user. Just make it Ajaxified. Do not post the entire form.Make an ajax call and get the result and show it to user.
Assuming you have jQuery loaded to your form.
#using(Html.BeginForm("GramsToOunce","Exercise5"))
{
<input name="grams" id="gram" type="text" placeholder="Insert grams" />
<input type="submit" id="btnSubmit" value="Convert Grams to Ounces" />
<div id="result"></div>
}
<script type="text/javascript">
$(function(){
$("#btnSubmit").click(function(e){
e.preventDefault();
var url=$(this).closest("form").attr("action");
$.post(url,{ grams : $("#gram").val()} ,function(res){
$("#result").html(res);
});
});
});
</script>
Let's make sure that our action method returns the value.
[HttpPost]
public string GramsToOunces(double? grams)
{
var result=grams * 0.035d;
return result.ToString();
}
Also if you simply want to do the multiplication, just do it in the client side(javascript). No need of a server call.

According to the documentation this line:
return View("GramsToOunces", "ounces");
Tries to return the view with the specified master page. However it seems there is no master page "ounces" in your project. So most likely what you need here is simply:
return View("GramsToOunces");
Note that you do not need to pass any models whatsoever because you are already using the ViewData.

Related

How to pass a model and a variable to a post method with one input

I'm trying to have one post route that takes care of multiple operations.
Here's the Controller:
[HttpPost("dish/{operation}")]
public IActionResult Dish(string operation, Dish dish)
{
if (operation == "RedEdit")
{
return RedirectToAction("EditDish", dish);
}
if (ModelState.IsValid)
{
if (operation == "Add")
{
_context.Add(dish);
_context.SaveChanges();
return RedirectToAction("Index");
}
else //Unused currently
{
Console.WriteLine("Oops");
_context.Add(dish);
_context.SaveChanges();
return RedirectToAction("Index");
}
}
else
{
return View("DishForm");
}
}
The POST route will take a string, which is the operation it'll do, and depending on the operation it'll run something different. Right now I don't have all the operations, and my else within the validation isn't what it's going to be. The problem I'm having currently is with the "RedEdit," which is just a method to redirect to the edit page. Here's my view and what I'd like to do:
#{
ViewData["Title"] = "Add a dish!";
ViewData["Header"] = "Add a new Dish!";
ViewData["Home"] = true;
ViewData["Add"] = false;
var parms = new Dictionary<string, string>
{
{"operation", ""}
};
}
#model RichCRUDelicious.Models.Dish
<div class="container d-flex flex-column text-center">
<h3><u>#Model.Name by #Model.Chef</u></h3>
<p>#Model.Description</p>
<p>Calories: #Model.Calories</p>
<p>Tastiness: #Model.Tastiness</p>
<footer>
#using (Html.BeginForm("Dish", "Home", "operation", FormMethod.Post)){
//Two Buttons with Edit and Delete
}
</footer>
</div>
I'd essentially like to have one form, which has two buttons, one for edit and delete. The button for edit will change my operation value in parms to "RedEdit," while delete will change it to "Delete" (which I don't have a route set up for currently but that's not the issue.) I've tried a couple different methods, and mostly the issue comes down to the parameters within the post method, I'm not sure how I can pass the model in AND the operation value. I don't mind if they're split up into two different forms, but I'd really like just one post method for this controller.
I've tried using a generic HTML Form with:
<form asp-action="Dish" asp-controller="Home" asp-all-route-data="parms" method="post">
But my issue wasn't resolved using this method either, I'm thinking a hidden input with two different forms will work, but if there's a better way I'd like to hear.
If you want to use a form with two buttons which go to the same action,you can try to add asp-route-operation to your buttons,here is a simple demo:
Dish:
public class Dish {
public int Id { get; set; }
public string Name { get; set; }
}
view:
<form method="post">
<input hidden name="Id" value="1" />
<input hidden name="Name" value="test" />
<button asp-controller="Home" asp-action="Dish" asp-route-operation="RedEdit">RedEdit</button>
<button asp-controller="Home" asp-action="Dish" asp-route-operation="Delete">Delete</button>
</form>
action:
[HttpPost("dish/{operation}")]
public IActionResult Dish(string operation, Dish dish)
{
...
}
}
Hidden inputs will help you pass the value of Dish to the action,asp-route-operation will help pass different operation values to the action.When clicking RedEdit button,the value of operation will be RedEdit.With Delete button,it will be Delete.

I don't understand why I have null MVC AJAX

I have Get and Post partial Action. Get take me a list of image which I have in ma app.
[HttpGet]
public PartialViewResult ViewImageFileList()
{
IEnumerable<string> allImages = Directory.EnumerateFiles(Server.MapPath("~/Images/NBAlogoImg/"));
return PartialView(allImages);
}
Post delete image which I extra.
[HttpPost]
public PartialViewResult ViewImageFileList(string imageNameType)
{
var fileToDeletePath = Path.Combine(Server.MapPath("~/Images/NBAlogoImg/"), imageNameType);
if (System.IO.File.Exists(fileToDeletePath))
{
fileOperations.Delete(fileToDeletePath);
}
return PartialView();
}
My .chhtml of my partial view
#model IEnumerable<string>
<div class="name-block-style">
Логотипы которые имеются
</div>
<div id=team-logo-wrapper-images>
<ul>
#foreach (var fullPath in Model)
{
var fileName = Path.GetFileName(fullPath);
<li>
<div class="box-name-image">
<p class="image-name-type">#fileName</p>
<img src="#Url.Content(string.Format("~/Images/NBAlogoImg/{0}", fileName))"
class="logo-images" alt="Логотип команды"
title="Логотип команды" />
</div>
</li>
}
</ul>
<div id="delete-image-form" class="form-group">
#using (Ajax.BeginForm(
"ViewImageFileList",
"Team",
new AjaxOptions() { HttpMethod = "POST", OnComplete = "reloadPage()" }))
{
<label>Введите имя с указание типа изображения</label>
<input type="text" class="form-group" name="imageNameType" id="imageNameType" />
<input type="submit" value="Удалить" class="btn btn-primary" />
}
</div>
<script>
function reloadPage() {
location.reload();
}
</script>
My problem is Null references when I write the deleting image and submit it(i do it by ajax). I have this error Null reference but when I click to continue, the image deleted and my script to reload page work.
I want to understand why I take the null and how I can fix it, because it stops my app always when I delete an image.
The problem is that when you POST after you delete the image you don't populate the model of the partial view, as you do correctly in ViewImageFileList. This has a result when the View Engine try to build the view that you would send after the POST to the client, to get a null reference exception when try to perform the foreach on a null reference.
That being said, the thing you need is to pass to the PartialView all the images. So just add before the return statement in the action method you POST this:
var allImages = Directory.EnumerateFiles(Server.MapPath("~/Images/NBAlogoImg/"));
return PatialView(allImages);
When you browsing images you return view with model passed
return PartialView(allImages); //allImages is a model
But when you deleting images you return view without any model
return PartialView(); //need to pass a model
So after deleting you would like to redirect to ViewImageFileList to browse
all images
[HttpPost]
public RedirectToRouteResult ViewImageFileList(string imageNameType)
{
var fileToDeletePath = Path.Combine(Server.MapPath("~/Images/NBAlogoImg/"), imageNameType);
if (System.IO.File.Exists(fileToDeletePath))
{
fileOperations.Delete(fileToDeletePath);
}
return RedirectToAction("ViewImageFileList");
}
or retrieve images in delete action once again and pass the list to view
[HttpPost]
public PartialViewResult ViewImageFileList(string imageNameType)
{
var fileToDeletePath = Path.Combine(Server.MapPath("~/Images/NBAlogoImg/"), imageNameType);
if (System.IO.File.Exists(fileToDeletePath))
{
fileOperations.Delete(fileToDeletePath);
}
IEnumerable<string> allImages = Directory.EnumerateFiles(Server.MapPath("~/Images/NBAlogoImg/"));
return PartialView(allImages);
}

Saving data from a html form to List

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.

How do I save my MVC4 model if I need 2 submit buttons?

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")
}

MVC3 - Multiple Ajax forms in a partial view are all filled with the same data on refresh

I'm learning MVC3 and building a little "to-do" website as a learning exercise, so I'm open to the idea that I'm just completely going down the wrong path!
Anyway, I have a page working perfectly with regular postbacks. I'm trying to Ajax it up with jQuery and UnobtrusiveAjax and everything still technically works correctly (the data is passed to the controller and saved in my database). The problem is that in the element I replace, each form's fields are all filled with the values that I just passed in on the one form.
Index.cshtml
#model WebUI.Models.HomeViewModel
#{
ViewBag.Title = "Index";
<script src="#Url.Content("~/Scripts/jquery-1.5.1.min.js")" type="text/javascript"></script>
<script src="#Url.Content("~/Scripts/jquery.unobtrusive-ajax.js")" type="text/javascript"></script>
}
<h2>My Goals</h2>
...snip...
<div id="goal_div">
#foreach (var goal in Model.Goals)
{
Html.RenderPartial("GoalDetail", goal);
}
</div>
GoalDetail.cshtml
#model Domain.Entities.Goal
<div class="goal" id='goal_id#(Model.ID)'>
<h4>#Model.Name</h4>
<p>#DateTime.Now.ToString()</p>
<p class="goal_description">#Model.Progress % complete</p>
<ul>
#foreach (var task in Model.Tasks)
{
using (Ajax.BeginForm("UpdateTask", "Home", new AjaxOptions { UpdateTargetId = "goal_id" + Model.ID }))
{
#Html.HiddenFor(g => g.ID)
#Html.Hidden("TaskID", task.ID)
<li class="task">
#Html.CheckBox("IsComplete", task.IsComplete)
#Html.TextBox("TaskName", task.Name)
#task.Name
<input class="actionButtons" type="submit" value="Update task" />
</li>
}
}
<li>
#using (Html.BeginForm("AddTask", "Home"))
{
#Html.HiddenFor(g => g.ID)
#Html.Editor("TaskName")
<input class="actionButtons" type="submit" value="Add task" />
}
</li>
</ul>
</div>
HomeController.cs
public class HomeController : Controller
{
private IGoalRepository Repository;
public HomeController(IGoalRepository repo)
{
Repository = repo;
}
public ViewResult Index()
{
HomeViewModel viewModel = new HomeViewModel();
viewModel.Goals = Repository.Goals;
return View(viewModel);
}
public ActionResult AddTask(int ID, string TaskName)
{
bool success = Repository.SaveTask(ID, 0, TaskName, DateTime.Today, false);
return RedirectToAction("Index");
}
public ActionResult UpdateTask(int ID, int TaskID, bool IsComplete, string TaskName)
{
bool success = Repository.SaveTask(ID, TaskID, TaskName, DateTime.Today, IsComplete);
Goal updatedGoal = Repository.Goals.FirstOrDefault(g => g.ID == ID);
return PartialView("GoalDetail", updatedGoal);
}
public ActionResult AddGoal(string Name, DateTime Enddate)
{
bool success = Repository.SaveGoal(0, Name, DateTime.Today, Enddate);
return RedirectToAction("Index");
}
public ActionResult UpdateGoal(int GoalID, string Name, DateTime Enddate)
{
bool success = Repository.SaveGoal(GoalID, Name, DateTime.Today, Enddate);
return RedirectToAction("Index");
}
}
I have the time there just to make sure that the AJAX refresh has actually happened, and you'll see why I have the task name there twice.
This is what I see when I first load the page:
Then I check the checkbox of the the 2nd task of the 1st goal, rename it "Updated Task #2", and click the update button. That's when this happens:
Seeing how the task names NOT part of the form are all correct (ignoring the re-ordering for now), and the progress value has been updated correctly (it just takes the completed tasks and divides by the total number of tasks), I have no idea why all the form values have been replaced. Even the AddTask form has been filled in, even though I haven't changed that one to use Ajax yet. I've been searching for reasons for this for 2 days now, and have come up empty.
After even more searching, I finally discovered the issue. Basically, it has to do with the way ModelState works in MVC. Reading this help thread and this article really helped me understand what was happening with my page. I ended up calling ModelState.Clear() in my controller right before returning the partial view, but this SO question and answer
suggests another method.

Categories

Resources