I have never done a POST request via Razor and MVC4. I think i have the core methods and stuff down but i am having difficulty fulfilling an actual POST request.
Here is the Razor View page code...
#model UserJob
#Html.HiddenFor(Model => Model.UserCode)
#Html.DropDownList("jobCode")
<input type="submit" value="Add" class="btn btn-default" />
And the method which i want to fulfil the POST method is.....
[HttpPost]
public ActionResult AddSkill(UserJob model)
{
db.UserJobs.Add(model);
db.SaveChanges();
return RedirectToAction("Jobs", new { UserCode = model.UserCode });
}
You Razor view needs to have the form. Either use #Html.BeginForm(...) to enclose your inputs, or just write HTML form markup yourself.
Related
.NET Core v6
MVC Web App with Entity Framework Core
I scaffolded a database table and it created a "db context", then I scaffolded default Controller and Views.
On the home page, I want to create a search box where you put in the primary key, or id. On submit, I want the value passed to the default Edit controller. Essentially, I want someone to be able to pull up a single record to Edit, if they know the primary key (id).
Here is what I have for a search box
<form asp-controller="MyTables" asp-action="Edit">
<p>
PIN: <input type="text" name="id" />
<input type="submit" value="Find" />
</p>
</form>
Here is the default Edit method inside the Controller.
// GET: MyTables/Edit/XYZ
public async Task<IActionResult> Edit(string id)
{
if (id == null)
{
return NotFound();
}
var MyTable = await _context.MyTables.FindAsync(id);
if (MyTable == null)
{
return NotFound();
}
return View(MyTable);
}
When I run this, it doesn't work, but I don't know how to set it up. How can I make this work? The only way I've been able to pull a record is by adding method="get" to the form, but then it creates a URL structure using "id" as a parameter like this localhost/MyController/Edit?id=XYZ. This structure won't work because I'm unable to save the record when I've navigated to the page in this way.
I appreciate any pointers or ideas.
Thank you
EDIT AFTER COMMENTS:
It has been suggested that I could go ahead and use the method="get", thereby selecting the correct record. The URL would be in this format: localhost/MyController/Edit?id=XYZ. A proposed solution is to fix my POST action to be able to save the record when using such URL paths. I'm not sure why, but it will only save right now if the URL path is in this format: localhost/MyController/Edit/XYZ (no parameter ?id =).
Here is a version of my default POST Edit action in the controller.
// POST: MyTables/Edit/5
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(string id, [Bind("...,...,...,...,...,...,PrimaryKeyField")] MyTable myTable)
{
if (id != myTable.PrimaryKeyField)
{
return NotFound();
}
if (ModelState.IsValid)
{
try
{
_context.Update(myTable);
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!MyTableExists(myTable.primaryKeyField))
{
return NotFound();
}
else
{
throw;
}
}
return RedirectToAction(nameof(Index));
}
return View(MyTable);
}
In the Edit view, to initiate the POST action, here's some of the Edit Form markup:
#model myApp.Models.MyTable
...
<form asp-action="Edit">
...
<div class="form-group">
<label asp-for="PrimaryKeyField" class="control-label"></label>
<input asp-for="PrimaryKeyField" class="form-control" />
<span asp-validation-for="PrimaryKeyField" class="text-danger"></span>
</div>
...
<div class="form-group">
<input type="submit" value="Save" class="btn btn-primary" />
</div>
The reason I'm saying it won't Save is when I click Submit on the Edit form, it brings up this HTTP error:
This localhost page can’t be found. No webpage was found for the web address: https://localhost:7020/MyTables/Edit
HTTP ERROR 404
In the Edit View, I added a reference to the Model.
#model myApp.Models.MyTable
In the form, I added a Tag Helper.
<form asp-action="Edit" asp-route-id=#Model.PrimaryKeyField>
This is working.
I noticed in the URL pattern of /Edit/XYZ, the form's asp-action uses this pattern. I noticed with the URL pattern of /Edit/?id=XYZ, the form's action uses /Edit with NO ID. Therefore, I figured out a way to add the id to the form's action by using the asp-route-id tag helper. The form submits the data properly now.
I have an issue when using a hidden field in an MVC Form Post. When the hidden field is generated via a HTML Helper it won't preserve it's value during the postback. But when using a HTML tag, it works.
Unfortunately this one has taken me a whole day to work out this work around.
Here is what I'm doing... (excuse any spelling, re-typed code for SO):
View Model
public class SomeViewModel
{
public int MyProperty1 { get; set; }
public int MyProperty2 { get; set; }
public int MyProperty3 { get; set; }
}
Post method
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult MyActionMethod(SomeViewModel someViewModel, string command)
{
...
...
// someViewModel.MyProperty1
...
...
}
View
#using (Html.BeginForm("MyActionMethod", "SomeController", FormMethod.Post, new { #class = "form-horizontal", role = "form" }))
{
#Html.AntiForgeryToken()
#Html.HiddenFor(m => m.MyProperty1)
<div class="col-md-2">
<input type="hidden" value=#Model.MyProperty1 name="MyProperty1" />
<input type="submit" name="command" value="Button1" class="btn btn-primary" />
<input type="submit" name="command" value="Button2" class="btn btn-success" />
</div>
<div class="col-md-1"></div>
<div class="col-md-1">
<input type="submit" name="command" value="Button3" class="btn btn-danger" />
</div>
<div class="col-md-8"></div>
}
In the above View code, the HTML helper hidden field (#Html.HiddenFor(m => m.MyProperty1)) does NOT work. But the HTML tag hidden field (input type="hidden" value=#Model.MyProperty1 name="MyProperty1") DOES work. I only have one or the other enabled. Both shown here are for display purposes.
I'd prefer to use the HTML Helper syntax, but can live with the HTML tag.
Things to note:
The View is using multiple submit buttons.
The View is using a partial view. Currently no content in the partial view and nothing is being done with it.
I can't see how these would affect the issue. Thought I'd mention it, in case.
Question: Can anyone explain why the HTML Helper isn't working?
***** UPDATE *****
Thanks to Stephen Muecke for pointing out what needed to be included in my question. Moreover an extra thank you for guessing what I was actually doing but I couldn't articulate it.
I'm updating the View Model property in the ActionMethod(), and when the same View is re-rendered, the View Model property doesn't reflect the new value.
Rather it is keeping it's initial value, and not preserving the new value.
Although not obvious and I found it difficult to find many articles on this subject to clarify it for me in the past the default behaviour in ASP.NET MVC is the following:
If you are using HTML Helpers and you are rendering the same View in response to a POST it is assumed that you are responding to failed form validation.
Therefore the values before being set in the POST will always be rendered back in the view from ModelState.
You have a few options:
ModelState.Clear(); in your post. Not recommended as the framework has not been designed this way.
Use the Post-Redirect-Get pattern and only display validation failure, as the framework was designed (as #StephenMuecke mentions).
If you are not bothered about validation do not use HtmlHelpers
Use Request.Form values instead and remove the SomeViewModel someViewModel parameter. Wouldn't recommend this as you lose all benefits of model binding.
Use ModelState.Remove for the specific field, again not recommended.
The best article I found on this was article from Simon Ince in 2010:
ASP.NET MVC’s Html Helpers Render the Wrong Value!
Another one by Rick Strahl:
ASP.NET MVC Postbacks and HtmlHelper Controls ignoring Model Changes
I am learning asp.net mvc framework
I create page with form. Data from this form going into database.
Then i return this page to user with all data from database. Easy
Ok.
Display page with form. Ok
Write data from this form to databse. Ok
Read data from databse and display it to user. Ok
Ok. But, then user refresh page, post method executes again with same form data and another data write to database. WTF?
Here code:
Controller:
public class MainController : Controller
{
[HttpGet]
public ActionResult Index()
{
return View(db.Entries.ToList());
}
[HttpPost]
public ActionResult Index(Message msg)
{
db.Entries.Add(msg);
db.SaveChanges();
return Index();
}
private MessagesContext db = new MessagesContext();
}
View:
#using BasicWeb.Models
#model List<Message>
#{
ViewBag.Title = "Index";
}
<h2>Сохранялка</h2>
<form method="post" action="">
<fieldset>
<legend>Введи бурду</legend>
<input type="text" name="UserName" maxlength="512"/>
<input type="submit" value="ВВОДИ МЕНЯ"/>
</fieldset>
</form>
<br />
#foreach(Message item in #Model)
{
<p>#item.UserName</p>
}
By refreshing the page after a POST request, you are actually re-submitting your form.
As a better practice, after submitting the form, make sure to redirect the user to the same form but as a GET request.
Read more about it here: Post/Redirect/Get (PRG)
You may want to track if the request is because of the page refresh.
Following may be helpful:
Page Refresh Causes Duplicate POST in ASP.NET Applications
You will need to check if the request is because of postback or pagerefresh.
I have a simple MVC application that retrieves DB Server, database, username and password from the user to store in an XML file. I want to add a "Test Connection" button to the screen and have it execute a method on the controller called TestConnection. The problem is the TestConnection method resets all the information on the screen when clicked since the View is being returned with no model. (because a GET operation is occurring). Here is my code:
From the Controller (named FrameworkConfigurationController.cs)
public ActionResult TestConnection()
{
return View("Index");
}
[HttpPost]
public ActionResult TestConnection(FrameworkConfigurationViewModel viewModel)
{
// TODO: Test will occur here
viewModel.DbConnectionMessage = string.IsNullOrEmpty(viewModel.DatabaseName) ? "Connection unsuccessful" : "Connection successful";
return View("Index", viewModel);
}
From my View (FrameworkConfiguration/Index):
#model Framework.ViewModels.FrameworkConfigurationViewModel
#{
Layout = "~/Views/Shared/_Layout.cshtml";
}
#using (Html.BeginForm()) {
#Html.ValidationSummary(true)
<fieldset>
<legend>FrameworkConfigurationViewModel</legend>
<div class="editor-label">
#Html.LabelFor(model => model.ServerName)
</div>
#* Edited for brevity *#
<button type="submit" onclick="location.href='#Url.Action("TestConnection")'">
Test Connection</button>
#Html.ValueFor(model => model.DbConnectionMessage)
<p>
<input type="submit" value="Save" />
</p>
</fieldset>
}
<div>
#Html.ActionLink("Back to Dashboard", "Index", "Home")
</div>
#section Scripts {
#Scripts.Render("~/bundles/jqueryval")
}
Admittedly, I am new to MVC programming. I'm coming from a Silverlight/MVVM background so the concept is not foreign to me...the disconnected nature of web programming is. I've followed some tutorials out there, but none of them seem to cover this type of thing - every example is contrived. I know how to do this with webforms and code-behind, but I would like to accomplish this with MVC.
Is there some way to "force" the POST operation instead of GET? I was under the impression that
<button type="submit">
accomplished that. Perhaps the implementation of the onclick I have written isn't correct. I am sure this is HTML 101 stuff, but I can't seem to find a simple answer to this.
Thanks for any help you can offer,
Jason
Why not have the Test Connection button trigger an Ajax action to call the controller, and display the result?
That way you avoid submitting your entire page.
In case you're not familiar with the details, here's a good overview on getting started with Ajax and MVC 4
http://ofps.oreilly.com/titles/9781449320317/ch_AJAX.html
I have a shared view in my _Layout.cshtml for my header named "_Header.cshtml".
I would like to display text and image from the database, so I need my controller to go in the database and return it to the _Header.cshtml.
How can I do that because the controller called is always different each page the user goes. Is there a way to have controller with Shared View?
Here is the _Layout.cshtml
<div id="header">
<div id="title">
#Html.Partial("_Header")
</div>
<div id="logindisplay">
#Html.Partial("_CultureChooser")
<br />
#Html.Partial("_LogOnPartial")
</div>
<div id="menucontainer">
#Html.Partial( "_MenuPartial")
</div>
</div>
<div id="main">
#RenderBody()
<div id="footer">
</div>
</div>
</div>
In your contoller action you could specify the name of the view:
public class MenuController : Controller
{
[ChildActionOnly]
public ActionResult Header()
{
var model = ... // go to the database and fetch a model
return View("~/Views/Shared/_Header.cshtml", model);
}
}
Now in your _Layout.cshtml instead of #Html.Partial("_Header") do this:
#Html.Action("Header", "Menu")
... 1 year later would just like to add one thing to Dimitrov answer. You can make the controller a little cleaner:
public class MenuController : Controller
{
[ChildActionOnly]
public ActionResult Header()
{
var model = ... // go to the database and fetch a model
return Partial("_Header", model);
}
}
Create an action in one of your controllers to render the header view, then simply call #Html.RenderAction("Header") within the _Layout.cshtml.
You can also pass a model into the RenderAction method if required.
While the RenderAction approach that WDuffy provided works well, I recently blogged about this very topic using another approach using IoC:
http://crazorsharp.blogspot.com/2011/03/master-page-model-in-aspnet-mvc-3-using.html
I hope the question you have asked is Like....
Can we have a controller for a Shared Layout View.
Simple answer is No.
To achieve this goal you have to create a partial view for the Same purpose and put it into you shared Layout. By that means you can achieve you Goal