So after looking around for guides and tutorials of how can I delete ASP Users, I found the following code to be pretty neat:
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> DeleteConfirmed(string id)
{
if (ModelState.IsValid)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
var user = await UserManager.FindByIdAsync(id);
var logins = user.Logins;
var rolesForUser = await UserManager.GetRolesAsync(id);
using (var transaction = context.Database.BeginTransaction())
{
foreach (var login in logins.ToList())
{
await UserManager.RemoveLoginAsync(login.UserId, new UserLoginInfo(login.LoginProvider, login.ProviderKey));
}
if (rolesForUser.Count() > 0)
{
foreach (var item in rolesForUser.ToList())
{
// item should be the name of the role
var result = await UserManager.RemoveFromRoleAsync(user.Id, item);
}
}
await UserManager.DeleteAsync(user);
transaction.Commit();
}
return RedirectToAction("Index");
}
else
{
return View();
}
}
My view looks something like this:
<td>
#Html.ActionLink("Edit", "Edit", new { id = user.UserId }) |
#Html.ActionLink("Delete", "DeleteConfirmed", new { id = user.UserId })
</td>
After clicking "Delete" here, in theory, it should have called the DeleteConfirmed method from the controller called "ManageUsersController". However, it returns this error:
Server Error in '/' Application.
The resource cannot be found.
Description: HTTP 404. The resource you are looking for (or one of its dependencies) could have been removed, had its name changed, or is temporarily unavailable. Please review the following URL and make sure that it is spelled correctly.
Requested URL: /ManageUsers/DeleteConfirmed/29ad177f-0285-43d2-b065-109876f270b9
What might be going wrong here? Is there another way that I should write the method in the controller? Thank you in advance
This answer is based on the default codes that .NET scaffold for us.
You're generating the Delete link using an extension of ActionLink which needs link text as the first parameter and action name as the second one. Your DeleteConfirmed action is a POST method; you can't generate a link to POST, .NET sees GET methods for links. So:
<td>
#Html.ActionLink("Edit", "Edit", new { id = user.UserId }) |
#Html.ActionLink("Delete", "Delete", new { id = user.UserId })
</td>
Make sure you have another method called Delete which is a GET one, in your controller.
Add another attribute to DeleteConfirmed:
[ActionName("Delete")]
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> DeleteConfirmed(string id)
{ ... }
If you need to delete the user when you click the Delete, you should use a POST form including the user id and anti-forgery token as hidden inputs and a submit button instead of link. But it's a best practice to show the user what they're deleting. That's why .NET generated two related actions for delete; Delete (to review) and DeleteConfirmed (to actually delete).
Related
I have a asp.net MVC project that when someone visits the home page it presents a number of buttons depending on which departments they work for.
They can then select the department they want to log in under.
The following controller then sets a session cookie with the appropriate contractId.
However if they login again under a different department the cookie doesn't get overwritten with the new contractId
public ActionResult SetContractId(int contractId)
{
Session["LoggedContractId"] = contractId;
return RedirectToAction("IndexLoggedIn");
}
I call the above from the buttons which are displayed in the view using the following:
#foreach (var item in Model)
{
<div class="col-lg-offset-4 col-md-8" style="margin-bottom: 25px">
#Html.ActionLink("Login under this Contract", "SetContractId", new { contractId = item.ContractId }, new { #Class = "btn btn-primary" }) <b>#Html.DisplayFor(modelItem => item.DepartmentName)</b>
</div>
}
I use this cookie value to set up the system for them. I use the session variable like this:
public ActionResult StartDebrief()
{
if (Session["LoggedContractId"] == null)
{
return RedirectToAction("IndexLogin", "Home", null);
}
else
{
var user = User.Identity.Name;
string userName = user.Substring(7);
var creator = Peopledb.People.FirstOrDefault(x => x.Username == userName);
var creatorContractId = (int)Session["LoggedContractId"];
//Use creatorContractId in the viewmodel and other areas
return View(viewModel);
}
}
Any ideas?
Your code to update the session information looks good in my opinion. This means that the session data should be updated correctly. However, it is possible the call to this function is not rightly in place. Try debugging the function SetContractId(). Where do you call this function, after a new login?
After editing a form and clicking a Save button, the HttpGet method is being executed before the HttpPost method. The page is reloading with the query string in the URL, and the old data still populating the fields, but the data has been saved on the server side. If I remove the query string and reload the page, the new data appears.
My expectation is that only the HttpPost method would be called, changes would be saved saved, then the page would be loaded back up with the saved changes.
Using the Microsoft.AspNetCore.Mvc": "1.0.0 package.
Here are my HttpGet and HttpPost methods:
[HttpGet]
[Route("~/Home/Activity/{activityId}")]
public IActionResult Activity(int activityId)
{
ViewData["Title"] = "Activity Detail";
FundraiserDBContext context = new FundraiserDBContext(_ServerName, EnvironmentCode);
Engagement activity;
if (activityId == -1)
{
activity = new Engagement();
context.Engagement.Add(activity);
}
else
{
activity = context.Engagement.FirstOrDefault(a => a.Id == activityId);
}
if (activity != null)
{
ActivityViewModel vmActivity = new ActivityViewModel(activity, context);
return View("Activity", vmActivity);
}
else
{
ActivityViewModel vmActivity = new ActivityViewModel(context);
return View("Activity", vmActivity);
}
}
[HttpPost]
[ValidateAntiForgeryToken]
//[Route("~/Home/Activity/{activityId}")]
public IActionResult Activity(ActivityViewModel vmActivity)
{
FundraiserDBContext db = new FundraiserDBContext(_ServerName, EnvironmentCode);
if (ModelState.IsValid)
{
db.Engagement.Update(vmActivity.ToEngagement(db));
db.SaveChanges();
}
return View("Activity", vmActivity); //this was vm.EngagementId
}
And here is the code for the Save button:
<button type="submit" class="btn-success pull-right" style="width:80px;" onclick="location.href='#Url.Action("Activity", "Home", #Model)'">Save</button>
Remove redirect from post method, because before returning the View its redirecting to the Index method without updated model
Redirect($"~/Home/Index"); // remove this line
Matjaž Mav found my error and described it in the comment below the original post. I mistakenly thought I needed the onclick event on my button. Removing this resulted in the expected behavior I was looking for.
The button code now looks like this:
<button type="submit" class="btn-success pull-right" style="width:80px;">Save</button>
I want to redirect to a previous page after I submit some data in database. I am using a TempData to retrieve ID on current Page, then I am going on the page with the form I want to submit (which have an ID), but after the POST method is fired TempData gets overrided by current page ID which is the page with the form and I want to ReturnRedirect to initial page with its ID.
Here the code:
Controller with GET method where I retrieve the ID:
var currentID = Url.RequestContext.RouteData.Values["id"];
TempData["currentId"] = currentID;
Controller with POST method where I try to redirect:
if (ModelState.IsValid)
{
// Editing records in database
....
return RedirectToAction("Details", "Jobs",
new { controller = "JobsController", action = "Details", id = TempData["currentId"] });
}
ModelState.AddModelError("", "Something failed");
return View();
I am using this approach because its working if the current action with a POST method doesn't have an ID.
Thank you for any suggestions.
EDIT:
I have a Details of Jobs:
// GET: Jobs/Details/5
public ActionResult Details(Guid id)
{
var currentID = Url.RequestContext.RouteData.Values["id"];
TempData["currentId"] = currentID;
var currentUserTemp = LoggedUserId;
TempData["userID"] = currentUserTemp;
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
Job job = db.Jobs.Find(id);
if (job == null)
{
return HttpNotFound();
}
return View(job);
}
But from this page I want a link to a page where I edit current user data.
View Details page Code:
#Html.ActionLink("Edit Company Details", "Edit", "UserAdmin", new { id = TempData["userID"] }, null)
Now I am on edit User Page, I edit the form and use save button (which triggers POST method) I want to use the other TempData variable to ReturnRedirect on Details Page but since both pages have ID`s, TempData gets overrided with last ID from the URL which is the ID of the user not ID.
Don't forget that TempData exists only during the time of a HTTP Request. Maybe it's a clue to your problem (I can't find all the interactions between Controllers and Views in your code so I can't be sure).
I have an #Html.ActionLink inside of a partial view that when clicked I'd like to have either send the user to another view or stay on the current view without changing anything. Is this possible?
Our controller looks like:
public ActionResult Edit(int id)
{
if (ShouldAllowEdit(id))
{
return this.View("Edit", ...edit stuff...)
}
return ????????
}
We tried return new EmptyResult(); but that just dumps the user to a blank page.
This is a little different approach to the issue, but it should do what you want.
Instead of giving the user a link to navigate to, do an ajax call on link/button click, and do the id check. Return either the url to navigate to in a JsonResult, or nothing if the id is invalid.
On return of the ajax call, navigate to the url if appropriate.
(swap out the hard coded ids and the == 0 with your ShouldAllowEdit function in the example of course)
In the View:
<div class="btn btn-danger" id="myButton">Button</div>
#section scripts{
<script>
$("#myButton").click(function () {
$.ajax("#Url.Action("Edit", new { id = 0 })", { type : "POST" })
.success(function (data) {
if (data.url !== "") {
window.location.href = data.url;
}
});
});
</script>
}
In the controller:
[HttpPost]
public JsonResult Edit(int id)
{
if (id == 0)
{
return Json(new {url = ""});
}
else
{
return Json(new { url = Url.Action("EditPage", new { id = id }) });
}
}
An answer is to redirect to the view action - and maybe give some feed back why they failed.
public ActionResult Edit(int id)
{
if (ShouldAllowEdit(id))
{
return this.View("Edit", ...edit stuff...)
}
ModelState.AddModelError("id", "Not allowed to edit this item");
return RedirectToAction(Edit(id));
}
If the user clicks a link they will be taken away. They might be sent back right to the same page, but the page will unload, be requested from the server again, and then re-rendered in the browser. If you don't want that to happen, you don't give the user the link in the first place. In other words, conditionally render the link or not based on the user's roles or whatever.
#if (userCanEdit)
{
#Html.ActionLink(...)
}
Where userCanEdit is whatever logic you need to make that determination.
If the user fails whatever check you determine, then they don't get the link. Simple.
However, since there's malicious people in the world, you can't just leave it entirely there. There's potential for the user to figure out the link to edit something and go there manually. So, to prevent that you check for the edit permission in your action (like you've already got in your code sample), but if the user is not allowed, then you just return a forbidden status code:
return new HttpStatusCodeResult(HttpStatusCode.Forbidden);
Or
return new HttpStatusCodeResult(403);
They both do the same thing.
UPDATE
Based on your comment above, it appears that the user is normally allowed to edit but can't in a particular instance because another user is editing. A 403 Forbidden is not appropriate in that case, so really all you've got is a simple redirect back to the page they were on, perhaps with a message explaining why they're back there.
TempData["EditErrorMessage"] = "Sorry another user is editing that right now.";
return RedirectToAction("Index");
I try to implement DELETE method for my mvc-application.
There is my link placed in View:
#Ajax.ActionLink("Delete", "Delete", "Users", new { userId = user.Id }, new AjaxOptions { HttpMethod = "DELETE" })
There is my controller method:
[HttpDelete]
public ActionResult Delete(string userId)
{
//...
return View("Index");
}
When I click on Delete-link, I get a 404 error.
In Fiddler I see, that my request perfomed by GET method!
If I execute DELETE request with the same Headers from Fiddler, I get the expected result - my request is coming right into the Delete method.
How can I use #Ajax.ActionLink correctly?
P.S.: I want to use only built-in methods.
Are you sure all the Unobtrusive libraries are loaded? An #Ajax.ActionLink generates a standard anchor tag. If the JavaScript libraries aren't loaded to handle the click event, you'll get the GET request you see in Fiddler.
Check to see if the jquery.unobtrusive-ajax.js script is included in a bundle that is referenced from your layout page or that you're explicitly loading it on specific pages in a scripts region.
Try this:
#Ajax.ActionLink("Delete", "Delete", "Users",
new { userId = user.Id },
new AjaxOptions { HttpMethod = "POST" })
I am not sure why you used 'Delete' for the HTTPMethod. Post will send the Id for data you want removed to the server and call the 'Delete' ActionResult specified here #Ajax.ActionLink("Delete", "Delete", "Users",.