ASP.NET MVC3 Partial View on HTTPPost - asp.net

I Have a View and Partial View. The layout for the view is something like this:
<html>
...
<div id="MainView">#RenderBody()</div>
<!--Partial View-->
<div id="partialView">#Html.Action("PartialViewForm", "Main")</div>
...
</html>
My Partial View (named as _Register) is something like this:
#model PartialViewModel
<div id="form">
#using (Html.BeginForm("PartialViewForm", "Main", FormMethod.Post))
{
#Html.ValidationSummary(true)
<table >
<tbody>
<tr>
<td align="left">#Html.LabelFor(model => model.Name)*</td>
<td>#Html.EditorFor(model => model.Name)</td>
<td align="left">#Html.ValidationMessageFor(model => model.Name, "")</td>
</tr>
<tr>
<td align="left"><input type="submit" value="Go" class="submit2"/></td>
</tr>
</tbody>
</table>
}
</div>
In my MainController I have methods like this:
public class MainController : Controller
{
public ActionResult Index()
{
return View();
}
[HttpGet]
public ActionResult PartialViewForm()
{
var partialViewModel= new PartialViewModel();
return PartialView("_Register", partialViewModel);
}
[HttpPost]
public ActionResult PartialViewForm(PartialViewModel partialViewModel )
{
// if Validation is not successfull
return PartialView("_Register", partialViewModel);
// else
....
}
}
This is what I want to do... when validation fails on the partial view I want to g back to the main view... however in my case on the post action when validation fails all I can see is the partialview... there is no main page content.
There are posts on the forum that show the same kind of behavior but I am not able to solve my issue. Can anyone please tell me how to fix it (it will be really helpful if you can modify my example and show it)
Thanks

I'm not sure if I totally understand what you are trying to do but if what I'm thinking is right, you should just use
[HttpPost]
public ActionResult PartialViewForm(PartialViewModel partialViewModel )
{
// if Validation is not successfull
model = _db.getBlah(); //get the original model for the main view
return View("MainView", model);
// else
....
}
However I think your issue might be that you really should have your form submission in your main view and not in your partial - the partial is just there to render the editors for your Create/Edit views, etc; the data should be submitted to the main view's action so that it can create/update the proper model.

Related

ASP.NET Editable Grid: Update register

I've been struggling with this for a while now. I'm constructing this view:
But when I hit the 'Update' button after do some changes the web refreshes to show the original values.
About the view: I get this view using an IEnumerable and looping thru each item in the model inside a form. Then, inside the form, there is a table that contains only 1 row. I do this in order to wrap all the items of the record in one form. This is part of code:
#foreach (var item in Model)
{
<form asp-action="Test" asp-route-id="#item.Id">
<table class="table">
<tbody>
<tr>
<td>
<input type="hidden" asp-for="#item.Id" />
<div class="form-group">
<div class="col-md-10">
<input asp-for="#item.MchName" readonly class="form-control" />
<span asp-validation-for="#item.MchName" class="text-danger"></span>
</div>
</div>
</td>
//more fields
<td>
<input type="submit" value="Update" class="btn btn-default" />
</td>
</tr>
</tbody>
</table>
</form>}
I declare an asp-action and a asp-route-id:
<form asp-action="Test" asp-route-id="#item.Id">
Question: Is this good enough? Is there something missing?
This is the Get Method:
public async Task<IActionResult> Test()
{
PopulateMachineTypeDropDownListStore();
return View(await _context.Machines.AsNoTracking().ToListAsync());
}
Question: I'm not passing any argument to the controller, yet the view will list the items following the given structure using an IEnumerable. Should I pass anything to the Get Method or is it fine as it is?
This is the Post Method:
#model IEnumerable<Application.Models.Machine>
[HttpPost, ActionName("Test")]
[ValidateAntiForgeryToken]
public async Task<IActionResult> TestPost(int? id)
{
if (id == null)
{
return NotFound();
}
var machinetoUpdate = await _context.Machines
.SingleOrDefaultAsync(s => s.Id == id);
if (await TryUpdateModelAsync(
machinetoUpdate,
"",
s => s.MchName, s => s.StoreID, s => s.PUnit, s => s.Status))
{
try
{
await _context.SaveChangesAsync();
}
catch (DbUpdateException)
{
ModelState.AddModelError("", "Unable to save changes. " +
"Try again, and if the problem persists, " +
"see your system administrator.");
}
return RedirectToAction("Test");
}
PopulateMachineTypeDropDownListStore();
return View(await _context.Machines.AsNoTracking().ToListAsync());
}
Question: I don't know if because the entity I retrieve the id from (and that I use to update the model thru TryUpdateModelAsync()) is also being used to compare to the model that thru the view this might not been working properly.
Thanks in advance for any help.

Error in rendering partial view in asp.net mvc ajax

In asp.net ajax, the partial view response replaces the html and only the partial view is rendered.
In the below snippet, when I click the ActionLink "Steps", the partial view is returned and replaces the Details.cshtml.
What I need is this partial view should replace only the "main-content-div" div.
Please help out. Thanks
View: Details.cshtml
<div class="main-body">
<table>
<tr>
<td class="left-sidebar extended">
#Ajax.ActionLink("Steps", "Steps", new { id = ViewBag.Id }, new AjaxOptions { AllowCache = false, UpdateTargetId = "main-content-div", InsertionMode=InsertionMode.Replace })
</td>
<td class="main-wrapper">
<div class="main-content" id="main-content-div">
This is the default stuff which I'll be replacing...
</div>
</td>
<td class="right-sidebar"></td>
</tr>
</table>
Action Method:
[HttpGet]
public PartialViewResult Steps(string id)
{
//reading model from db
return PartialView("~/Views/Author/Exercise/_StepsPartial.cshtml", Model);
}
Partial View:
#model IEnumerable<Model>
<div>
<div>
<input type="text" />
</div>
</div>
You need to add jquery.unobtrusive-ajax.min.js to your project for this to work.
Go to the Nuget Console Package Manager console and type:
Install-Package Microsoft.jQuery.Unobtrusive.Ajax
You then just need to add a reference at the top of your _Layout.cshtml or View to the relevant script:
<script src="~/Scripts/jquery.unobtrusive-ajax.min.js"></script>

Thymeleaf send parameter from html to controller

I'm newbie in Thymeleaf. I'm trying to create simple crud application. I'm trying to delete object of Customer class on delete button. How can I set parameter(for example - id) to the method which called deleteUser using Thymeleaf. Here's my controller.
package controllers;
//imports
#Controller
public class WebController extends WebMvcConfigurerAdapter {
#Autowired
private CustomerDAO customerDAO;
#Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/results").setViewName("results");
}
//show all users
#RequestMapping(value="/users", method=RequestMethod.GET)
public String contacts(Model model) {
model.addAttribute("users",customerDAO.findAll());
return "list";
}
//show form
#RequestMapping(value="/users/add", method=RequestMethod.GET)
public String showForm(Customer customer) {
return "form";
}
//add user
#RequestMapping(value="/users/doAdd", method=RequestMethod.POST)
public String addUser(#RequestParam("firstName") String firstName,
#RequestParam("lastName") String lastName,
#RequestParam("lastName") String email) {
customerDAO.save(new Customer(firstName, lastName, email));
return "redirect:/users";
}
//delete user
#RequestMapping(value="users/doDelete/{id}", method = RequestMethod.POST)
public String deleteUser (#PathVariable Long id) {
customerDAO.delete(id);
return "redirect:/users";
}
}
Here's my view.
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>Getting Started: Serving Web Content</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
</head>
<body>
List of users
Add new user
<table>
<tr>
<th>Id</th>
<th>First Name</th>
<th>Last Name</th>
<th>Email</th>
<th>Action</th>
</tr>
<tr th:each="user : ${users}">
<td th:text="${user.id}">Id</td>
<td th:text="${user.firstName}">First name</td>
<td th:text="${user.lastName}">Last Name</td>
<td th:text="${user.email}">Email</td>
<td>
<form th:action="#{/users/doDelete/}" th:object="${customer}" method="post">
<button type="submit">Delete</button>
</form>
</td>
</tr>
</table>
</body>
</html>
You do not need form to do this:
<td>
<a th:href="#{'/users/doDelete/' + ${user.id}}">
<span>Delete</span>
</a>
</td>
Blejzer answer is an easy and straight forward solution unless you are also working with spring security (always recommended) in which case you should prefer POST instead of GET for all modification operations such as delete to protect against CSRF attack. This is exactly why spring recommends logout be done like this only. In order for you to adapt to POST, change your controller to read this parameter from request parameters instead of path variable
//delete user
#RequestMapping(value="users/doDelete", method = RequestMethod.POST)
public String deleteUser (#RequestParam Long id) {
customerDAO.delete(id);
return "redirect:/users";
}
Add a hidden field in the form which posts with the id as name and it's value in hidden parameter
<form th:action="#{/users/doDelete}" th:object="${customer}" method="post">
<input type="hidden" th:field="${id}" />
<button type="submit">Delete</button>
</form>
On a side note, even if you are not using spring security, it is always recommended to use post for any entity modification operations (like delete or update). Saves you from lots of trouble on web in long run. Take a look at Quick Checklist for Choosing HTTP GET or POST for detailed information.
path variables can be set as:
<a th:href="#{/users/doDelete/__${user.id}__}"><span>Delete</span></a>

ASP.net MVC 4 global Authorize filter forcing login on an AllowAnonymous action

In my .NET MVC 4 I'm adding a global filter in order to secure my controllers.
This is done using:
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new HandleErrorAttribute());
filters.Add(new System.Web.Mvc.AuthorizeAttribute());
}
Which is invoked from my Global.cs class.
My Web.config file contains a standard configuration:
<authentication mode="Forms">
<forms loginUrl="~/Account/Login" timeout="2880" />
</authentication>
My Login action is decorated with [AllowAnonymous] in order to allow anonymous users to login.
So far so good, everything works great. Here is my Login action:
[AllowAnonymous]
public ActionResult Login(string returnUrl)
{
ViewBag.ReturnUrl = returnUrl;
return View();
}
Now, I wanted to add a reset-password page which also just like the login page should be available to anonymous users. I created me action (under the same controller of the Login action) and decorated it with the [AllowAnonymous] decoration:
[AllowAnonymous]
public ActionResult ResetPasswordForUser(string message = "")
{
ViewBag.message = message;
return View();
}
Then created the corresponding view.
I added this link to my Login page:
#Html.ActionLink("Forgot your password?", "ResetPasswordForUser", "Account")
In runtime, as I click this link using an anonymous user I do get to the ResetPasswordForUser action, but when returning the view the Login action gets invoked and I can never actually get to the desired view. For some reason my request gets intercepted even though I'm using the [AllowAnonymous] decoration.
Am I missing something here?
Thanks in advance
Update1:
As per Darin Dimitrov request adding my ResetPasswordForUser view:
#using TBS.Models
#model TBS.ViewModels.ResetPasswordForUserViewModel
#{
ViewBag.Title = "Reset Password";
}
#using (Html.BeginForm())
{
#Html.ValidationSummary(true)
<table class="edit-table rounded bordered" style="width: 400px">
<tr>
<td class="label-td">
#Html.LabelFor(m => m.Username)
</td>
<td>
#Html.TextBoxFor(m => m.Username)
#Html.ValidationMessageFor(model => model.Username)
</td>
</tr>
<tr>
<td class="label-td">
#Html.LabelFor(m => m.Password)
</td>
<td>
#Html.Password("Password")
#Html.ValidationMessageFor(model => model.Password)
</td>
</tr>
<tr>
<td colspan="2" style="text-align: center">
<input type="submit" value="Reset" />
</td>
</tr>
</table>
}
Is it possible that the _Layout file used by the ResetPasswordForUser view is invoking an Action in your Controller (e.g. in a menu?) that hasn't been decorated with AllowAnonymous?
This would cause this type of behaviour.
It sounds like you could have a non-anonymous #Html.Action or Html.RenderAction in your layout, which is causing your page to redirect to login.
Edit: Deleted old answer as it was incorrect and not useful for anyone coming to see it.
I don't know if adding that global authorize filter conflicts with AllowAnonymous. The way I set up my project is to have a Base Controller that has the [Authorize] attribute set at the controller level.
All my controllers inherit from this BaseController except for ones that require anonymous access, usually my AccountController and my MarketingController. Those simply inherit from Controller and then I can set [Authorize] at the Action level.

MVC3 Checkbox passing values from view back to model

I have a view model which is just
public class Visits
{
public List<Visit> visits { get; set; }
}
In my visit model I have a
public bool ValidVisit { get; set; }
I am able to pass everything to my view ok and render all of the visits on the view. The view looks like
#model TheWallSite.ObjectModels.Visits
#{
ViewBag.Title = "Potential invalid visits!";
Layout = "~/Views/Shared/_Layout.cshtml";
}
#using (Html.BeginForm())
{
<fieldset>
<table>
<tr><th>Check In/Out Time</th><th>Visit Type</th><th>In/Out</th><th>IP</th><th>SSO ID</th><th>Valid Visit</th></tr>
#foreach (var item in Model.visits)
{
<tr>
<td>#Html.DisplayFor(model => item.InOutTime)</td>
<td>#Html.DisplayFor(model => item.VisitType)</td>
<td>#Html.DisplayFor(model => item.VisitName)</td>
<td>#Html.DisplayFor(model => item.IP)</td>
<td>#Html.DisplayFor(model => item.SSO)</td>
<td>#Html.EditorFor(model => item.ValidVisit)</td>
</tr>
}
</table>
<input type="submit" value="Submit" />
</fieldset>
}
The problem I am having is I want the end user to be able to check/uncheck the ValidVisit and then pass these back to the controller and make the correct changes in my database.. I am having a heck of a time figuring out how to do this. Any suggestions? My [HttpPost] controller signature is
public ActionResult ListQuestionableVisits(Visits model, FormCollection forms)
but nothing seems to be getting back to the controller.
It would be the model-binding not kicking in, probably due to the loop.
I know i know, it should work, but do it the right way, and it has a better chance of working.
Try using editor templates instead.
/EditorTemplates/Visit.cshtml
#model TheWallSite.ObjectModels.Visit
<tr><td>#Html.DisplayFor(model => model.InOutTime)</td></tr>
<tr><td>#Html.DisplayFor(model => model.VisitType)</td></tr>
<tr><td>#Html.DisplayFor(model => model.VisitName)</td></tr>
<tr><td>#Html.DisplayFor(model => model.IP)</td></tr>
<tr><td>#Html.DisplayFor(model => model.SSO)</td></tr>
<tr><td>#Html.EditorFor(model => model.ValidVisit)</td></tr>
Main View:
#model TheWallSite.ObjectModels.Visits
#{
ViewBag.Title = "Potential invalid visits!";
Layout = "~/Views/Shared/_Layout.cshtml";
}
#using (Html.BeginForm())
{
<fieldset>
<table>
<tr>
<th>Check In/Out Time</th>
<th>Visit Type</th>
<th>In/Out</th>
<th>IP</th>
<th>SSO ID</th>
<th>Valid Visit</th>
</tr>
#Html.EditorFor(model => model.Visits)
</table>
<input type="submit" value="Submit" />
</fieldset>
}
Also, if that's your complete view, you don't need the FormCollection parameter in the action, unless there is a hidden field/some other magic field i'm not seeing.

Resources