ASP.NET MVC: Method for HttpPost is not executed - c#

In an ASP.NET MVC application, I have a file MessageController.cs where I define EditMessage for access via HttpGet and HttpPost. Usually the user first accesses it via HttpGet, then a form pops up where he can edit the message, and then he clicks on the Save button, by which HttpPost will be invoked.
My problem is that HttpPost is not invoked. Instead, an error message is displayed. I have analogous code for modifying other parts of the database and with that analogous code, HttpPost works. My question is why it does not work here and what I can do to make it work.
/// <summary>
/// used by admin only
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
[CustomAuthorization(new[] { GlobalStaticFunc.SecurityOptions.isSUser, GlobalStaticFunc.SecurityOptions.isAdmin })]
[HttpGet]
public async Task<ActionResult> EditMessage(int id)
{
if (PopupController.AnyPopupsInline(User))
{
return RedirectToAction("index", "popup");
}
if (id > 0)
{
BLogic_Entity dbContext = new VMIEntityCreator(true);
var msg = dbContext.GetDbSet<MSG_MESSAGES>().Where(x => x.id == id).FirstOrDefault();
if (msg != null) return View(msg);
}
else if (id == -1)
{
return View(new MSG_MESSAGES() { id = -1 });
}
return View("Messages");
}
[CustomAuthorization(new[] { GlobalStaticFunc.SecurityOptions.isCarrier, GlobalStaticFunc.SecurityOptions.isAdmin, GlobalStaticFunc.SecurityOptions.isSUser })]
[HttpPost]
// [ValidateAntiForgeryToken]
public async Task<ActionResult> EditMessage(MSG_MESSAGES model)
{
if (PopupController.AnyPopupsInline(User))
{
return RedirectToAction("index", "popup");
}
if (!App_Tools.RightsHandler.IdentityWatcher.CheckUserRights(User.Identity, GlobalStaticFunc.SecurityOptions.isAdmin) && App_Tools.RightsHandler.IdentityWatcher.CheckUserRights(User.Identity, GlobalStaticFunc.SecurityOptions.isEndUser))
{
return RedirectToAction("Messages", "Message");
}
bool isOk = false;
if (model != null)
{
if (!ModelState.IsValid)
{
return View(model);
}
if (model.id > 0)
{
using (TED_BLOGIC.Abstractions.BLogic_Entity usr = new VMIEntityCreator(true))
{
isOk = await usr.UpdateSecurely(usr.GetDbSet<MSG_MESSAGES>().Where(x => x.id == model.id).FirstOrDefault(), model, ModelState);
}
}
}
return View(model);
}
The code of EditMessage.cshtml:
#model TED_BLOGIC.DataBase.DB_MODEL.MSG_MESSAGES
#{
if (Model != null && Model.id > 0)
{
ViewBag.Title = "Message bearbeiten";
}
else
{
ViewBag.Title = "Neue Message anlegen";
}
ViewBag.Ico = "fa-plus";
Layout = "~/Views/Shared/_standardBoxView.cshtml";
}
#using (Html.BeginForm("EditMessage", "Message", new { id = Model.id }, FormMethod.Post, new { data_goback = true }))
{
#Html.AntiForgeryToken()
<div class="panel-body">
<div class="row">
<div class="col-md-12 table-responsive">
#Html.ValidationSummary(false, "", new { #class = "text-danger" })
#Html.EditorForModel("Add/MGV")
<div class="section row mb10">
<div class="form-group">
<div class="col-md-offset-2 col-lg-3 col-md-4 col-sm-5">
<input type="submit" value="Save" class="btn btn-default" onclick=";" /> #*mCust.postToSite(#Url.Action("User", "Admin"));mCust.sendMeBack()*#
</div>
</div>
</div>
</div>
</div>
</div>
}
<script src="~/Scripts/Core/PostbackHandling/OverwritePostbacks.js?v=#GlobalStaticFunc.Version"></script>
<script>
$(document).on("EverythingIsReady", function () {
document.title = 'Cloud - #ViewBag.Title';
})
</script>

<input type="submit" value="Save" class="btn btn-default" onclick=";" />
Could you please try removing onclick attribute of the button?

please check by commenting CustomAuthorization attribute above that post method, the post-event will be fired. I tested your code.

Related

ASP.NET MVC Core Error -> DbUpdateConcurrencyException: Database operation expected to affect 1 row(s) but actually affected 0 row(s)

I know there are a few questions of this error posted but none of them helped me!
I'm using ASP.NET MVC Core and Identiy and I'm trying to update Roles. But it's getting a bit nerve wrecking because I can't find out what's the problem with my code.
The error is the following:
DbUpdateConcurrencyException: Database operation expected to affect 1 row(s) but actually affected 0 row(s). Data may have been modified or deleted since entities were loaded. See http://go.microsoft.com/fwlink/?LinkId=527962 for information on understanding and handling optimistic concurrency exceptions.
screen of the error is display here
My controller:
public async Task<IActionResult> Edit(string id)
{
if (id == null)
{
return NotFound();
}
//var role = await roleManager.FindByIdAsync(id);
var role = await roleManager.Roles.FirstOrDefaultAsync(f => f.Id == id);
if (role == null)
{
return NotFound();
}
return View(role);
}
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(string id, [Bind("Id,Name")] IdentityRole role)
{
if (id != role.Id)
{
return NotFound();
}
if (ModelState.IsValid)
{
try
{
_context.Entry(role).State = EntityState.Modified;
await roleManager.UpdateAsync(role);
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
catch (DbConcurrencyException ex)
{
throw new DbConcurrencyException(ex.Message);
}
}
return View(role);
}
My View :
#model Microsoft.AspNetCore.Identity.IdentityRole
#{
ViewData["Title"] = "Edit";
}
<h1>Edit</h1>
<h4>Roles</h4>
<hr />
<div class="row">
<div class="col-md-4">
<form asp-action="Edit">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<input type="hidden" asp-for="#Model.Id" />
<div class="form-group">
<label asp-for="#Model.Name" class="control-label"></label>
<input asp-for="#Model.Name" class="form-control" />
<span asp-validation-for="#Model.Name" class="text-danger"></span>
</div>
<div class="form-group">
<input type="submit" value="Save" class="btn btn-primary" />
</div>
</form>
</div>
</div>
<div>
<a asp-action="Index">Back to List</a>
</div>
#section Scripts {
#{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}
I would really appreciate some help getting this error fixed.
Your code is not correct. replace this:
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit([Bind("Id,Name")] IdentityRole role)
{
if (ModelState.IsValid)
{
var roleItem = await _roleManager.FindByIdAsync(role.Id);
if (roleItem != null)
{
roleItem.Name = role.Name;
var updateResult = await _roleManager.UpdateAsync(roleItem);
if (updateResult.Succeeded)
{
return RedirectToAction(nameof(Index));
}
else
{
//Handle errors
}
}
else
{
//Handle errors
}
}
return View(role);
}

Url seems to be repeating

I am using asp.net razor engine. I have a delete button and the first time I press it, it works. The second time the url repeats the function and does not work.
This is the first time I use the Delete button
This is the second time I use Delete. Notice the URL is del/del. Trying to avoid that second del.
Here is my controller
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using System.Linq;
using Microsoft.AspNetCore.Mvc;
using DapperApp.Factory;
using login.Models;
using login.Controllers;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Http;
namespace login.Controllers
{
public class HomeController : Controller
{
private readonly UserFactory userFactory;
public HomeController(UserFactory user) {
userFactory = user;
}
// GET: /Home/
[HttpGet]
[Route("")]
public IActionResult Index()
{
return View();
}
[HttpPost]
[Route("")]
public IActionResult Register(Home model)
{
if(!ModelState.IsValid)
{
return View("Index", model);
}
PasswordHasher<Home> Hasher = new PasswordHasher<Home>();
model.Password = Hasher.HashPassword(model, model.Password);
userFactory.Add(model);
TempData["message"] = false;
return RedirectToAction("Index");
}
[HttpPost]
[Route("login")]
public IActionResult Login(Home model)
{
if(model.Password == null || model.Email == null){
TempData["login"] = false;
return RedirectToAction("Index");
}
var pass = userFactory.FindByEmail(model);
var Hasher = new PasswordHasher<Home>();
if(pass == null)
{
TempData["login"] = false;
return RedirectToAction("Index");
}
// Pass the user object, the hashed password, and the PasswordToCheck
if(0 != Hasher.VerifyHashedPassword(model, pass.Password, model.Password))
{
TempData["first_name"] = pass.First_Name;
TempData["last_name"] = pass.Last_Name;
TempData["id"] = pass.Id;
HttpContext.Session.SetString("Id", pass.Id.ToString());
ViewBag.Quotes = userFactory.FindAll();
return View();
}
TempData["login"] = false;
return RedirectToAction("Index");
}
[HttpPost]
[Route("addQuote")]
public IActionResult AddQuote(Quotes model)
{
var test = HttpContext.Session.GetString("Id");
if(!ModelState.IsValid)
{
TempData["id"] = test;
model.Users_id = Convert.ToInt32(test.ToString());
var user2 = userFactory.FindById(model.Users_id);
TempData["first_name"] = user2.First_Name;
TempData["last_name"] = user2.Last_Name;
ViewBag.Quotes= userFactory.FindAll();
return View("Login", model);
}
if(test == null){
return RedirectToAction("Index");
}
model.Users_id = Convert.ToInt32(test.ToString());
userFactory.addQuote(model);
var user = userFactory.FindById(model.Users_id);
TempData["id"] = test;
TempData["first_name"] = user.First_Name;
TempData["last_name"] = user.Last_Name;
ViewBag.Quotes = userFactory.FindAll();
return View("Login", model);
}
[HttpGet]
[Route("logout")]
public IActionResult Logout()
{
return RedirectToAction("Index");
}
[HttpGet]
[Route("del/{id}")]
public IActionResult Del(int Id) // This is my delete method
{
userFactory.DeleteByID(Id);
ViewBag.Quotes2= userFactory.FindAll();
var test = HttpContext.Session.GetString("Id");
var user = userFactory.FindById(Convert.ToInt32(test));
TempData["first_name"] = user.First_Name;
TempData["last_name"] = user.Last_Name;
TempData["id"] = test;
return View("Login");
}
}
}
Here is my cshtml page
<h1>Hello #TempData["first_name"] #TempData["last_name"]</h1>
#if(TempData["first_name"]!= null)
{
}
#model login.Models.Quotes
<h1>Add Your Quote</h1>
#using(Html.BeginForm("AddQuote","Home"))
{
<p>
<label>Your Quote</label>
#Html.TextAreaFor(d=>d.quotes)
#Html.ValidationMessageFor(d => d.quotes)
</p>
<input type="submit" name="submit" value="Add my quote!"/>
}
<form action="logout" method="get">
<input type="submit" name="submit" value="Log Out"/>
</form>
<div >
#{
if(ViewBag.Quotes != null)
{
foreach(var quotes in ViewBag.Quotes)
{
//If there are any errors for a field...
<p><q>#quotes.quotes</q></p>
<p class="wrapper">-#quotes.First_Name #quotes.Last_Name at #quotes.Created_At.ToString("hh"):#quotes.Created_At.ToString("mm")
#quotes.Created_At.ToString("tt") #quotes.Created_At.ToString("MMM") #quotes.Created_At.ToString("dd")
#quotes.Created_At.ToString("yyyy")</p>
if(#quotes.Users_id == Convert.ToInt32(TempData["id"].ToString()))
{
<form action="del/#quotes.Id_Quotes" method="get">
<input type="submit" name="submit" value="Delete"/>
</form>
}
}
}
if(ViewBag.Quotes2 != null)
{
foreach(var quotes in ViewBag.Quotes2)
{
//If there are any errors for a field...
<p><q>#quotes.quotes</q></p>
<p class="wrapper">-#quotes.First_Name #quotes.Last_Name at #quotes.Created_At.ToString("hh"):#quotes.Created_At.ToString("mm")
#quotes.Created_At.ToString("tt") #quotes.Created_At.ToString("MMM") #quotes.Created_At.ToString("dd")
#quotes.Created_At.ToString("yyyy")</p>
if(#quotes.Users_id == Convert.ToInt32(TempData["id"].ToString()))
{
<form action="del/#quotes.Id_Quotes" method="get">
<input type="submit" name="submit" value="Delete"/>
</form>
}
}
}
}
</div>
Your form action is using a relative (not a concrete) reference, meaning it will append the action to the end of the current url each time you submit the form. Try making the action the absolute url of your get request. In this case that would mean:
<form action="del/#quotes.Id_Quotes" method="get">
<input type="submit" name="submit" value="Delete"/>
</form>
becomes
<form action="/del/#quotes.Id_Quotes" method="get">
<input type="submit" name="submit" value="Delete"/>
</form>
Also, just to nitpick, when deleting things (or adding and editing things) to a database you should use a POST request. They provide an additional level of security.

Unable to edit IdentityRole

I have the following view:
#model Microsoft.AspNet.Identity.EntityFramework.IdentityRole
#{
ViewBag.Title = Resources.Edit;
}
<h2>#Resources.EditRole</h2>
#Html.ActionLink(Resources.ListRoles, "Index") | #Html.ActionLink(Resources.ManageUserRoles, "ManageUserRoles")
<hr />
<div class="row">
<div class="col-md-8">
<section id="editRoleForm">
#using (Html.BeginForm("Edit", "Role", new { ReturnUrl = ViewBag.ReturnUrl }, FormMethod.Post, new { #class = "form-horizontal", role = "form" }))
{
#Html.AntiForgeryToken()
<h4>#Resources.Role</h4>
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
#Html.HiddenFor(model => model.Id)
<div class="form-group">
#Html.LabelFor(m => m.Name, new { #class = "col-md-2 control-label" })
<div class="col-md-10">
#Html.TextBoxFor(m => m.Name, new { #class = "form-control" })
#Html.ValidationMessageFor(m => m.Name, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="#Resources.Save" class="btn btn-default" />
</div>
</div>
}
</section>
</div>
</div>
I also have the following two methods in my RoleController:
//
// GET: /Role/Edit/5
public ActionResult Edit(string Role)
{
var thisRole = context.Roles.Where(r => r.Name.Equals(Role, StringComparison.CurrentCultureIgnoreCase)).FirstOrDefault();
return View(thisRole);
}
//
// POST: /Role/Edit/5
[HttpPost]
public ActionResult Edit(FormCollection collection)
{
try
{
var thisRole = context.Roles.Where(r => r.Id.Equals(collection["Id"])).FirstOrDefault();
thisRole.Name = collection["Name"];
context.SaveChanges();
return RedirectToAction("Index");
}
catch
{
return View();
}
}
Originally, I was trying to use this method instead of the second one:
//
// POST: /Role/Edit/5
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit(IdentityRole Name)
{
try
{
context.Entry(Name).State = System.Data.Entity.EntityState.Modified;
context.SaveChanges();
return RedirectToAction("Index");
}
catch
{
return View();
}
}
But I never got it to work because the Name parameter was always null -which I still don't know why it happened, so if someone can explain it to me that'll be greatly appreciated.
I wrote then the other method since I saw the use of FormCollection in another example (to create roles) and it seems to work fine, at least it contains the information I need when I debug it. My issue is that although collection["id"] has the right Id for the Role I'm trying to edit, context.Roles is completely empty. This makes no sense to me given that when the first method is called (loading the View for the first time), this line
var thisRole = context.Roles.Where(r => r.Name.Equals(Role, StringComparison.CurrentCultureIgnoreCase)).FirstOrDefault();
returns the selected role (out of the several that exist and that I can see when I add to watch context.Roles). However, after the view is loaded, the textbox edited and the second method in the controller gets called, context.Roles has nothing in it. Why?
Ok you can use the already built in [Authorize(Roles="RoleType")] filter.
You then have your regular User model and Account controller so you can then authorize users. Once you have authorised them you can set them to a specific role.
E.g. user story: only admins can access action result x
[Authorize(User="Admin")]
Public ActionResult X(){
...
}
That way you simply assign user roles in the model creation.
E.g.
Public Class UserModel
{
int id {get;set;}
string name {get;set;}
string Role {get;set;}
.....
}
Now only users that have been authorised AND are of Role type "Admin" will be able to access that controller.
If you want to edit their role you can do a simple edit user action method
e.g.
[Post]
public actionresult edituser(int? id)
{
if (id == null)
{
return HttpNotFound();
}
using (var db = new UserContext())
{
UserModel editUser = db.UserModel.Find(id);
if (editUser == null)
{
return HttpNotFound();
}
db.User(editModel).State = System.Data.Entity.EntityState.Modified;
db.SaveChanges();
}
RedirectToAction("Action", "Controller");
}
Now any user that is NOT of Role type "Admin" will not be able to access that screen. They will receive a 404 error.

Multiple submit buttons with 2 actions which share the same information

I have a problem about how to use 2 actions which must share an value in a view which contains 2 submit buttons. In a "Delete" view, I want to have to action : delete the person or desactivate the person (desactivate means assigning an end date to his contract).
Here is my submit buttons :
#using (Html.BeginForm()) {
<p>
<input type="submit" value="Delete"/>
<input type="submit" value="Desactivate" />
</p>
#Html.ActionLink("Back to List", "Index")
}
And there are my actions :
public ActionResult Delete(long id = 0)
{
Person person = db.Persons.Single(p => p.Id_Person == id);
if (person == null)
{
return HttpNotFound();
}
return View(person);
}
//
// POST: /Person/Delete/5
[HttpPost, ActionName("Delete")]
public ActionResult DeleteConfirmed(long id)
{
Person person = db.Persons.Single(p => p.Id_Person == id);
db.Persons.DeleteObject(person);
db.SaveChanges();
return RedirectToAction("Index");
}
[HttpPost]
public ActionResult Desactivate(long id)
{
Person person = db.Persons.Single(p => p.Id_Person == id);
person.EndDate = DateTime.Now;
db.Persons.Attach(person);
db.ObjectStateManager.ChangeObjectState(person, EntityState.Modified);
db.SaveChanges();
return RedirectToAction("Index", "Person");
}
I tried to separate my submit button into different forms but it didn't work and it's normal because I need to use the same id for the delete action and the desactivate action.
Any idea?
Try this
#using (Html.BeginForm()) {
<p>
<input type="submit" class="delete" value="Delete"/>
<input type="submit" class="deactivate" value="Desactivate" />
</p>
#Html.ActionLink("Back to List", "Index")
}
<scirpt type="text/javascript">
$(function(){
$(".delete").click(function (){
$(this).parents("form").action = "ControllerName/DeleteConfirmed";
return true;
});
$(".deactivate").click(function (){
$(this).parents("form").action = "ControllerName/Desactivate";
return true;
});
});
</script>

Adding a variable into the HTML.BeginForm Model

I am having issues retaining the passwordToken between my GET Controller and my View. I see that the token is passed and added to the model correctly within the GET Controller but as soon as the HTML.BeginForm starts in the View the model has a new instance and the previous model with the passwordToken is lost. I need the passwordToken to be retained in order to use WebSecurity.ResetPassword. Any suggestions on how this could be done?
My GET Controller:
[AllowAnonymous]
public ActionResult PasswordReset(string passwordToken)
{
// Token Validation
var usrID = WebSecurity.GetUserIdFromPasswordResetToken(passwordToken);
var usr = _dbManager.GetUserInformation(usrID);
if (usr == null)
{
//The link you are using is not valid anymore
return RedirectToAction("Error", "Account");
}
else
{
var model = new PasswordReset();
model.PasswordResetToken = passwordToken;
return View(model);
}
}
My View:
#model Project.Models.PasswordReset
#{
ViewBag.Title = "Password Reset";
}
<h2>Password Reset</h2>
<div class="form passwordreset-form">
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
#Html.ValidationSummary()
<div class="input-form">
<div class="inputbox-label">
#Html.LabelFor(m => m.Password)
</div>
<div class="inputbox">
#Html.PasswordFor(m => m.Password)
</div>
<div class="inputbox-label">
#Html.LabelFor(m => m.ConfirmPassword)
</div>
<div class="inputbox">
#Html.PasswordFor(m => m.ConfirmPassword)
</div>
</div>
<div style="float:right;">
<input type="submit" value="Change Password" />
</div>
}
</div>
My POST Controller:
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public ActionResult PasswordReset(PasswordReset model)
{
//Attemp to change password
var passwordChangeConfirmation = WebSecurity.ResetPassword(model.PasswordResetToken, model.Password);
//Password has been changed
if(passwordChangeConfirmation == true)
{
return RedirectToAction("Index", "Home");
}
//Password change has failed
else
{
return RedirectToAction("Error", "Account");
}
}
I ended up adjusting the POST class to make it work.
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public ActionResult PasswordReset(PasswordReset model, string passwordToken)
{
//Attemp to change password
model.PasswordResetToken = passwordToken;
var passwordChangeConfirmation = WebSecurity.ResetPassword(model.PasswordResetToken, model.Password);
//Password has been changed
if (passwordChangeConfirmation == true)
{
return RedirectToAction("Index", "Home");
}
//Password change has failed
else
{
return RedirectToAction("Error", "Account");
}
}
add it into your form:
#Html.HiddenFor(m => m.PasswordResetToken);
You can use a hidden input on the form for the field (from your model) that you pass it.
#Html.HiddenFor(m => m.PasswordResetToken);
in output
<input type="hidden" name="PasswordResetToken"></input>

Categories