ASP.NET MVC5 Html.Beginform not calling action method - c#

I had my submit form working from my razor file to a controller, then from the controller to my remote database. but now i don't even think the controller class is being called.
Here is my view:
#model InputEvent
#using (Html.BeginForm("Save", "Portal/Controllers/MyEvent"))
{
<md-input-container class="md-block" flex-gt-xs="">
<label>Title</label>
#Html.TextBoxFor(m => m.title)
</md-input-container>
<md-input-container class="md-block">
<label>Address</label>
#Html.TextBoxFor(m => m.address)
</md-input-container>
<md-button class="md-raised">
<input type="submit" value="Save" />
</md-button>
}
with my model:
public class InputEvent
{
public string title;
public string address;
}
And my controller with the database connection:
namespace Portal.Controllers
{
public class MyEventController : Controller
{
[HttpPost]
public ActionResult Save(InputEvent y)
{
MySqlConnection conn = new MySqlConnection("mydbstring");
string myTitle = y.title;
string myAddress = y.address;
conn.Open();
MySql.Data.MySqlClient.MySqlCommand comm = conn.CreateCommand();
comm.CommandText = "INSERT INTO event(title, address) VALUES(#title, #address)";
//comm.Parameters.AddWithValue("#title", myTitle);
//comm.Parameters.AddWithValue("#address", myAddress);
comm.Parameters.AddWithValue("#title", "test_title");
comm.Parameters.AddWithValue("#address", "test_address");
comm.ExecuteNonQuery();
conn.Close();
return View();
}
}
}
Am i not calling my controller correctly? or is my sql command invalid?
EDIT: I just checked my database again over an hour later, and i have multiple rows with "test_title" and "test_address" in there. i guess my code works, but it is VERY delayed. This might not be the best place to ask, but does anyone have any idea why it could be so delayed inserting into the DB?

Ensure the server-side code looks like the code below. Note the [HttpPost] attribute. While the default for client-side form is Post, HttpGet is the default for the server-side. So you would have to explicitly say you want a HttpPost on the server-side. Do the following steps. Note Save method has two overloads and also one with HttpGet and the other with HttpPost. When you are done, put a break point on the method with the HttpPost attribute and post the form. You will see that the model will be hydrated.
Step 1
#using (Html.BeginForm("Save", "MyEvent", FormMethod.Post))
{
<md-input-container class="md-block" flex-gt-xs="">
<label>Title</label>
#Html.TextBoxFor(m => m.title)
</md-input-container>
<md-input-container class="md-block">
<label>Address</label>
#Html.TextBoxFor(m => m.address)
</md-input-container>
<md-button class="md-raised">
<input type="submit" value="Save" />
</md-button>
}
Step 2
public class MyEventController : Controller
{
[HttpPost]
public ActionResult Save(InputEvent model)
{
// Consider refining the implementation to use Stored Procedures or an ORM e.g. Entity Framework.
// It helps secure your app. Application security advice.
MySqlConnection conn = new MySqlConnection("mydbstring");
conn.Open();
MySql.Data.MySqlClient.MySqlCommand comm = conn.CreateCommand();
comm.CommandText = "INSERT INTO event(title, address) VALUES(" + model.title + "," + model.address + ")";
comm.ExecuteNonQuery();
conn.Close();
return View();
}
[HttpGet]
public ActionResult Save()
{
return View();
}
}

Add the FormMethod.Post as a third parameter... your action method is specting a Post and not a Get.
#using (Html.BeginForm("Save", "MyEvent", FormMethod.Post)){
....
}

I see that your code is working now. However, you should not be returning a view from a POST method the way you are. You should use PRG pattern. Read that link so you are aware of the issues your code can cause. I know that is for MVC 4 but the pattern is the same regardless of the version of MVC.

Related

Delete item from db with paramenrs

I have some problems with deletion item from database (SQLServer) using parameters for that. I want to press "Delete" reference in Index() then put name parameter in Delete() and redirect action to Index() again and show content of db. When I press "Delete" reference I show nothing but start page of Index() :(
public async Task<IActionResult> Delete(string nm)
{
IQueryable<Phone> users = db.Phones;
if (!String.IsNullOrEmpty(nm))
{
users = users.Where(p => p.Name.Contains(nm));
foreach (var item in users)
{
db.Phones.Remove(item);
}
await db.SaveChangesAsync();
}
return RedirectToAction("Index");
}
#model DataApp2.Models.Phone
#{
ViewBag.Title = "Delete";
}
<form method="get">
<div class="form-inline form-group">
<label class="control-label">Name: </label>
#Html.TextBox("nm", Model.Name, htmlAttributes: new { #class = "form-control" })
<input type="submit" value="Delete" class="btn btn-default" />
</div>
</form>
Building the input yourself and using a form is a bit overkill/overcomplicated. Instead, you can leverage the .NET MVC framework to send the request to your action
by replacing the form you posted and everything inside of it with:
#Html.ActionLink("Delete", "Delete", new { nm = Model.Name })
This will generate a link (<a> tag) with the text "Delete" (first param of the ActionLink) and send the Model.Name in a data field called nm to the Delete action in your controller (second param of the ActionLink).
I've put together a proof of concept showing that this works:
View:
#Html.ActionLink("Delete", "Delete", new { nm = "hi" })
Controller Action:
public ActionResult Delete(string nm)
{
if (!String.IsNullOrEmpty(nm))
{
ViewBag.Name = nm;
}
return RedirectToAction("Index");
}
the controller is successfully setting ViewBag.Name in this example. Note as far as the issue you're having, it makes no difference that I'm returning a ActionResult here instead of async Task<IActionResult> as you are.
I'm guessing that you're not populating Model.Name in the action that initially loads the page. Please post the code for your get action that loads the view if you'd like more information. You can test this theory by sticking:
#if (string.IsNullOrEmpty(Model.Name))
{
<h1>Name is empty!</h1>
}
else
{
<h1>Name is #Model.Name</h1>
}
in your view if you dont want to step through the code via the debugger

ASP.NET Core MVC How to send ViewModel to different controller method

So I have 2 controller classes;
AnnouncementsController, this just generates a homepage of sorts with posts from users on it.
// GET: Announcements
public async Task<IActionResult> Index()
{
var announcements = await _context.Announcement.ToListAsync();
announcements = announcements.OrderByDescending(x => x.CreatedOn).ToList();
foreach (var ann in announcements)
{
ann.TimeAgo = ann.CreatedOn.TimeAgo();
}
var users = await _context.Users.ToListAsync();
var comments = await _context.Comment.ToListAsync();
AnnouncementAndCommentsViewModel aacVM = new AnnouncementAndCommentsViewModel();
AnnouncemenstViewModel announcemenstViewModel = new AnnouncemenstViewModel();
announcemenstViewModel.Announcement = announcements;
announcemenstViewModel.User = users;
announcemenstViewModel.Comment = comments;
CommentViewModel commentViewModel = new CommentViewModel();
aacVM.announcemenstViewModel = announcemenstViewModel;
aacVM.commentViewModel = commentViewModel;
ViewData.Add("currentUserID",GetCurrentUser().Id);
return View(aacVM);
}
Then I have the CommentsController
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create(CommentViewModel commentViewModel)
{
if (ModelState.IsValid)
{
var comment = new Comment();
comment.CreatedOn = DateTime.Now;
comment.Body = commentViewModel.Comment.Body;
Announcement announcement = GetAnnouncement(commentViewModel.Announcement.AnnouncementID);
comment.Announcement = announcement;
ApplicationUser user = GetCurrentUser();
comment.User = user;
_context.Add(comment);
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
return View(commentViewModel);
}
From my AnnouncementController Index view, I have a list of posts and then I want to be able to write and add comments directly in that view as opposed to going to another view.
I have 2 ViewModels, one for storing announcements and one for comments.
I'm trying to call the Create Post method in the Comments Controller with its argument that accepts a CommentViewModel although I can't figure it out.
I keep getting sent to the Create Get Method with the body filled in.
Here's my index view and how I'm trying to post the data to the CommentsController
#model Treharris.Models.AnnouncementAndCommentsViewModel;
.
.
.
#foreach (var item in Model.announcemenstViewModel.Announcement)
{
.
.
.
<div class="announcement-body">
<p>#Html.DisplayFor(modelItem => item.Body)</p>
</div>
<div class="comments">
<p id="comment">Comments</p>
#using(Html.BeginForm("Create","Comments", FormMethod.Post,
new { cvm = Model.commentViewModel }))
{
##Model.commentViewModel.Announcement = item;
#Html.HiddenFor(m => m.commentViewModel.Announcement)
#Html.TextBoxFor(m => m.commentViewModel.Comment.Body,
new { placeholder = "Comment body" })
<input type="submit" value="Create" />
}
As #Shyju commented, for model binding to work using the form extensions, the Model that you pass to the View has to be the same that the post action receives.
If you look at the HTML that gets generated,
#Html.HiddenFor(m => m.commentViewModel.Announcement)
outputs something as
<input type="hidden" name="commentViewModel.Announcement" value="propVal" />.
On the Action you expect a CommentViewModel object that has no property named commentViewModel, but has a property named Announcement. That is why the binding does not occur.
Either change the parameter on the post action method to match the View Model, but be aware that all properties that do not exist on the form are posted as null,
OR
drop the Html form extension methods that are being deprecated in .NET Core and use simple Html for these type of bindings as the follow:
<form asp-controller="Comments" asp-action="Create" method="post">
<input type="hidden" name="Announcement" value="#Model.commentViewModel.Announcement" />
<input type="text" name="Comment.Body" placeholder="Comment body" value="#Model.commentViewModel.Comment.Body" />
<input type="submit" value="Create" />
</form>
Remember to include all the information regarding the Announcement object as hidden fields, as you cannot post complex objects on forms as you are trying to do. That is an HTML feature/limitation.
For example, simple include the announcement id in the form as:
<input type="hidden" name="Announcement.Id" value="#item.id" />

MVC Maintaining Model through multi view form submits

So I am working on a MVC which is basically three steps.
Create a view for each step i.e.
StepOne
StepTwo
StepThree
On step one and two I ask the users to enter some details.
All the values for the multiple step I store in one Model.
And getting from StepOne to StepTwo is fine. Certain values in my model are being set and maintained.
But on StepTwo when I do my second httppost and pass the model, it seems to just create a new instance of the model and values from stepone are not maintained.
<% using (Html.BeginForm("StepTwo", "Home", FormMethod.Post, new { id = "restrictionForm" })) { %>
<%: Html.AntiForgeryToken() %>
<div id="wrapping" class="clearfix">
<h3>Postcode Restriction Type : </h3>
<%= Html.DropDownListFor(x => x.SelectedRestriction, Model.RestrictionTypes,"Select Restriction...", new { #class = "selmenu required" }) %>
<h3>Restriction Description : </h3>
<%= Html.TextBoxFor(m => m.RestrictionDescription, new { #class = "txtblock required" }) %>
</div>
<section id="buttons">
<input type="submit" value="Submit" id="submitBtn" />
</section>
And in my controller
On Page Load my Model is still intact and still maintains values from previous step.
[Authorize]
public ActionResult StepTwo(PostcodesModel model)
{
var summaryMessage = "";
model.SummaryMessage = summaryMessage;
model.RestrictionTypes = _Provider.GetRestrictionTypes();
return View(model);
}
But at the Httppost, the model has lost values and seems to have created new instance of model.
[Authorize]
[HttpPost]
[ActionName("StepTwo")]
[ValidateAntiForgeryToken]
public ActionResult StepTwoPost(PostcodesModel model)
{
return View(model);
}
Any idea how I can maintain model between Http Posts ?
It seems from your question that you believe models persist across requests. This is not true.
You either pass information to the view via your model from the controller, or submit values from your view to your controller and MVC handles this by binding html form inputs to your View Model.
If you want to persist your View Model across each step you need to take the values accepted and copy them into a new model (or directly inject it) when calling your new view.
Something like this (I just typed this up off my head so its not clean but should give you an idea):
[HttpGet]
public ActionResult StepOne()
{
var model = new MyNewModel();
return View(model);
}
/* NOTE THE MODEL PASSED BACK HERE IS NOT THE EXACT SAME OBJECT
AS THE ONE CREATED IN THE GET ACTION ABOVE, MODEL BINDING HAS OCCURRED
TO READ YOUR FORM INPUTS AND MATCH THEM TO A NEW MODEL WHICH IS EXPECTED */
[HttpPost]
public ActionResult StepOne(MyNewModel model)
{
if (ModelState.IsValid)
{
// Do something here
// pass model to new view
TempData["model"] = model;
return RedirectToAction("StepTwo");
}
return View(model);
}
[HttpGet]
public ActionResult StepTwo()
{
MyNewModel model;
if (TempData["model"] != null)
{
model = (MyNewModel) TempData["model"];
// make some changes if necessary
model.MyProperty = 2;
return View(model);
}
return RedirectToAction("StepOne");
}
I think you can also keep your model in Session ( per application ) or in a ViewState ( per page ).
Every time you make a post you upgrade the session. It's also optimal because on the client side you receive only a session identifier.
Some differences between Session and Viewstate:
Session is per application, while ViewState is per page
Session sends to the client side only a session identifier, while ViewState sends an ecrypted text

Hide parameter value from URL

I have a URL like this
http://localhost/PW/LeaveWithoutPay/Edit?id=9
and I want to hide the id?=9 from my URL. Can any one demonstrate how to hide this id parameter with an example? I am using Visual Studio 2012.
You must need to implement Post method instead of GET method. Here is a sample example for it.
In your controller define something like this
public ActionResult Edit([FromBody] int id) {
TempData["MsgText"] = id.ToString();
return RedirectToAction("Index");
}
Now in your view, implement the POST method. A sample example is:
#{string id =(string)TempData["MsgText"];}
#using (Html.BeginForm("Edit", "Home", FormMethod.Post, new { id = "frmCallThis" })){
#Html.Label("label",string.IsNullOrEmpty(id)?"No Id Provided":"Current ID = " + id)
#Html.TextBox("id");
<input type="submit" value="Get This Printed" />
}
Finally you have the following output: (Before Submit)
And After submit:
Hope this helps,
Only one thing you have to doing here is using POST, not GET method. Because the web request is usually stateless, so I don't think we have any other methods to hide your id.

razor mvc4 and c# codebehind

Alright, so I have just started with razor mvc4 and I have a little experience with c#. I am currently making a website on which there is a button. my html follows:
<button onclick ="vote1_click" id="VoteButton" value="Vote">Vote</button>
this is in a .cshtml view
I then have a class to handle the vote1_click event. It is in c# and follows:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
namespace MvcApplication1
{
public class voting
{
public void vote1_click(object sender, EventArgs e)
{
}
}
}
I believe that my issue is a fundamental understanding of the razor structure, but could not figure it out on my own.
Any help at all is appreciated, and I will try to not feel too stupid when the answer is simple.
Thanks!
EDIT:
I have been getting an issue where the Add(string name) gives me an error of "not all code paths return a value"
here is the rest of my code as requested:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Data.SqlClient;
namespace WAgermanClub.Controllers
{
public class HomeController : Controller
{
[HttpPost]
public ActionResult Add(string vote)
{
SqlConnection vote1connection = new SqlConnection("user id=userid;" +
"password=validpassword;server=o5z5dpwpzi.database.windows.net;" +
"Trusted_Connection=yes;" +
"database=wagermanclub_votes; " +
"connection timeout=30");
try
{
vote1connection.Open();
}
catch (Exception g)
{
Console.WriteLine(g.ToString());
}
try
{
SqlDataReader myReader = null;
SqlCommand myCommand = new SqlCommand("select * from table", vote1connection);
myReader = myCommand.ExecuteReader();
while (myReader.Read())
{
Console.WriteLine(myReader["Vote1"].ToString());
}
}
catch (Exception i)
{
Console.WriteLine(i.ToString());
}
SqlCommand vote1command = new SqlCommand("INSERT INTO table (Column1, Vote1) " +
"Values (1, 'Vote1' + 1)", vote1connection);
vote1command.ExecuteNonQuery();
try
{
vote1connection.Close();
}
catch (Exception h)
{
Console.WriteLine(h.ToString());
}
}
}
}
And here is my HTML:
#{
ViewBag.Title = "Ideas";
}
#section featured {
<section class="featured">
<div class="content-wrapper">
<hgroup class="title">
<h1>#ViewBag.Title.</h1>
<h2>#ViewBag.Message</h2>
</hgroup>
<p>
</p>
</div>
</section>
}
<body>
<div style="border: solid; max-width: 300px; margin-left: auto; margin-right: auto">
#using(Html.BeginForm())
{
<input type="submit" value="Vote"/>
}
</div>
</body>
Thanks!
You are confused with ASP.NET webforms and MVC. MVC works more in the classic Web (GET-POST form) style. You post a form with values. There is no such click events and event handler in codebehind like what you have in web forms.
So to render your page, you may have an action method like this in your HomeController
public class HomeController : Controller
{
public ActionResult Add()
{
return View();
}
}
So in your Add view(razor file), you need to have some code to render a form tag with input elements. Let's use the Html.Begin form helper method to render the form tag for us.
#using(Html.Beginform())
{
<input type="text" name="name" />
<input type="submit" />
}
This will render a form tag in your markup with action property set as "Home/Add", assuming your GET action method is in HomeController. (Check the view source of the page)
So when user clicks on the submit button it will post the form to the Add action. so make sure you have an action method like this in HomeController to handle the form posting.(The one decorated with HttpPost attribute
[HttpPost]
public ActionResult Add(string name)
{
//do something with the posted values
return RedirectToAction("Success"); // redirecting to another view
}
You may be confusing the webforms model with the asp.net mvc model.
razor is only available for you when using webpages or asp.net mvc.
For asp.net mvc there's not concept of a server method/event as you've defined here.
You'll typically need to define action methods in your controllers which will be responsible for processing whatever forms your are posting.
You may want to check out more on ASP.NET MVC
MVC does not implement the web forms style viewstate and event handlers. So there is no vote1_click. What you would want to do is either
1) Create a JavaScript Post/Get back to the server
or
2) have a form and post back all the form variables back to the server
Here is a pretty good example of beginning MVC: http://www.asp.net/mvc/tutorials/mvc-4/getting-started-with-aspnet-mvc4/intro-to-aspnet-mvc-4

Categories