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.
Related
First of all my codes:
ManageClass.cshtml:
#{
ViewData["Title"] = "Class' Management";
}
<br />
<h2>Add Class</h2>
<form method="post" asp-controller="AddClassDB" asp-action="">
<div class="container">
<div class="form-group">
</div> </div>
<label for="formGroupExampleInput2">Class Name</label>
<input type="text" class="form-control" id="classNameInput">
<br/>
<div class="float-right">
<button type="submit" class="btn btn-success">Add</button>
</div>
<br />
<h2>Manage Class</h2>
</form>
HomeController.cs:
using Microsoft.AspNetCore.Mvc;
using StudentWeb.Models;
using System.Diagnostics;
namespace StudentWeb.Controllers
{
public class HomeController : Controller
{
private readonly ILogger<HomeController> _logger;
public HomeController(ILogger<HomeController> logger)
{
_logger = logger;
}
public IActionResult Index()
{
return View();
}
public ActionResult ManageClass()
{
return View();
}
public ActionResult AddClassDB(ClassTable _table)
{
Console.WriteLine(_table);
return View();
}
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
public IActionResult Error()
{
return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
}
}
}
I will take the value of classNameInput in ManageClass.cshtml and save it to SQL. I will do the saving in the Controller, but I have not yet received the value entered by the user.
But after I enter the value in the input and press the submit button, I get the following result:
(page not found)
You are using the wrong value for asp-controller and asp-action for the form. Hence it generates the wrong action path for the form.
It should be:
<form method="post" asp-controller="Home" asp-action="AddClassDB">
...
</form>
By default, all the action methods in the controller are GET (method). You need to apply [HttpPost] attribute so that the AddClassDB is recognized as POST (method).
[HttpPost("AddClassDB")]
public ActionResult AddClassDB(ClassTable _table)
{
Console.WriteLine(_table);
// TO-DO Redirect to view that is existed
return View();
}
i'm making a webbapplication with ASP.NET MVC and im trying to edit my list of objects. If i for example add a product to the site and then click on edit for that product to change the prize i just get a new object with the new prize instead of changing the prize to the product.
So the problem is that instead of updating the products it just adds a new one.
this is how my controller for the products looks like:
using auktioner_MarcusR91.Data;
using auktioner_MarcusR91.Models;
using Microsoft.AspNetCore.Mvc;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace auktioner_MarcusR91.Controllers
{
public class InventoryController : Controller
{
private readonly AppDbContext _db;
public InventoryController(AppDbContext db)
{
_db = db;
}
public IActionResult Index()
{
IEnumerable<Inventory> objInventoryList = _db.Inventories;
return View(objInventoryList);
}
//GET
public IActionResult Create()
{
return View();
}
//Post
[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult Create(Inventory inventory)
{
_db.Inventories.Add(inventory);
_db.SaveChanges();
return RedirectToAction("index");
}
//GET
public IActionResult Edit(int? id)
{
if (id == 0 || id == 5)
{
return NotFound();
}
var inventoryFromDb = _db.Inventories.Find(id);
if (inventoryFromDb == null)
{
return NotFound();
}
return View(inventoryFromDb);
}
//Post
[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult Edit(Inventory inventory)
{
if (ModelState.IsValid)
{
_db.Inventories.Update(inventory);
_db.SaveChanges();
return RedirectToAction("index");
}
return View(inventory);
}
}
}
I think there is something wrong in my controller.
However here is also my view for when i edit a product:
#model Inventory
<form method = "post" asp-action = "Edit">
<div class = "border p-3 mt-4">
<div class = "row pb-2">
<h2 class = "text-primary">Edit Inventory</h2>
<hr />
</div>
<div class = "mb-3">
<label asp-for ="inventoryName"></label>
<input asp-for = "inventoryName" />
<label asp-for ="finalPrize"></label>
<input asp-for = "finalPrize" />
<label asp-for ="inventoryDesc"></label>
<input asp-for = "inventoryDesc" />
<p>1 för "Transport</p>
<p>2 för "Smycken"</p>
<p>3 för "Hushåll"</p>
<p>4 för "Dekoration"</p>
<select asp-for = "categoryId">
<option>1</option>
<option>2</option>
<option>3</option>
<option>4</option>
</select>
</div>
<button type = "submit" class = "btn btn-primary" width = "100px">Update</button>
<a asp-controller = "Inventory" asp-action = "index" class = "btn btn-secondary" style = "width: 100px">Back to products</a>
</div>
</form>
You have to add a primary key inventoryId as a hidden input, without this key , you inventory instance looks like a new one for EF.
And since you are using [ValidateAntiForgeryToken] in the action, add this to view with another form syntax
#using (Html.BeginForm("Edit", "Inventory", FormMethod.Post))
{
#Html.AntiForgeryToken()
<input type="hidden" asp-for="inventoryId" value="#Model.inventoryId" />
....
<button type = "submit" class = "btn btn-primary" width = "100px">Update</button>
<a asp-controller = "Inventory" asp-action = "index" class = "btn btn-secondary" style = "width: 100px">Back to products</a>
</div>
}
and IMHO your update code could be like this
if (ModelState.IsValid)
{
var inventoryFromDb = _db.Inventories.Find(inventory.inventoryId);
if (inventoryFromDb == null)
{
return NotFound();
}
_db.Entry(inventoryFromDb).CurrentValues.SetValues(inventory);
var result= _db.SaveChanges();
}
You have to send your record id to the controller by clicking update button of the record . something like this :
<a class="btn btn-warning btn-sm btn-margin" asp-controller="ContextController" asp-action="UpdateAction" ***asp-route-id="#item.Id***">Update</a>
which #item is the object of the model sent to the view .
And the action would be :
[HttpGet]
public IActionResult UpdateAction(int id)
{
Model record = _Context.GetById(id);
return View("UpdateFormPageOrModal",record);
}
And after updating the form and clicking the submit button of the view data will send to action :
[HttpPost]
public IActionResult UpdateAction(Model record)
{
var result = _Context.UpdateBy(record);
ViewData["Result"] = result.Message;
if (result.IsSucceeded)
{
_UnitOfWork.Save();
return RedirectToAction("TheGridView");
}
return View("UpdateView",record);
}
where UpdateBy() method should be like this :
public void UpdateBy(T entity)//entity is an object of the DbSet<Model>
{
var state = _Context.Entry(entity).State;
if (state == EntityState.Detached)
{
_Context.Attach(entity);
}
_Context.Entry(entity).State = EntityState.Modified;
}
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.
I tried to add a comment in Detail View, but it doesn't work when I input some text in TextArea. Throwing NullReferenceException in
if (id != ticket.TicketId)
{
return NotFound();
}
I created ViewModel -> contain (Ticket and Comment)
<div class="form-group">
<form asp-action="Details">
<label asp-for="Comment" class="control-label"></label>
<textarea asp-for="Comment.Content" class="form-control" placeholder="Add comment!"></textarea>
<span asp-validation-for="Comment.Content" class="text-danger"></span>
<input type="submit" value="Add comment" class="btn btn-default" />
</form>
</div>
Here is an example how I try to provide some text, bind to Comment, and here is my Detail method in Controller:
public async Task<IActionResult> Details(int id, TicketCommentViewModel model)
{
var ticket = model.Ticket;
if (id != ticket.TicketId)
{
return NotFound();
}
var oldTicket = await _context.Tickets.SingleOrDefaultAsync(t => t.TicketId == ticket.TicketId);
var comment = new Comment();
var user = await GetCurrentUserAsync();
oldTicket.Comments.Add(comment);
comment.Content = model.Comment.Content;
comment.SendTime = DateTime.Now;
comment.TicketID = ticket.TicketId;
comment.Ticket = ticket;
comment.UserId = user.Id;
comment.User = user;
if (ModelState.IsValid)
{
try
{
await _context.Comments.AddAsync(comment);
_context.Update(oldTicket);
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!TicketExists(oldTicket.TicketId))
{
return NotFound();
}
else
{
throw;
}
}
return RedirectToAction(nameof(Index));
}
return View(model);
}
I've been playing with some of the MVC tutorials on making a database website with C# and have a question about how to make a section of the website only accessible ONCE a user has logged in with a username and password.
I have a logon page (code below), which takes the username and password, and then authenticates a user against a database record. Once you've clicked the "login" button the return URL takes you into an Admin section of the website (full path is: http://localhost:53559/Data/update). This bit I'm happy with. However, the issue I have is that the "update" page is still accessible if you've NOT logged in I.e. if I enter in the browser the path above (http://localhost:53559/Data/update) without ever logging in first it will load no problem).
How do I restrict the update page, so it is only available once the user has logged in?
(NB: total beginner, small words please!)
==================================================================================
Controller code for the logon:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Web.Routing;
using System.Web.Security;
using DFAccountancy.Models;
namespace DFAccountancy.Controllers
{
public class AdminController : Controller
{
//
// GET: /Admin/LogOn
public ActionResult LogOn()
{
return View();
}
//
// POST: /Account/LogOn
[HttpPost]
public ActionResult LogOn(LogOnModel model, string returnUrl)
{
if (ModelState.IsValid)
{
if (Membership.ValidateUser(model.UserName, model.Password))
{
FormsAuthentication.SetAuthCookie(model.UserName, model.RememberMe);
if (Url.IsLocalUrl(returnUrl) && returnUrl.Length > 1 && returnUrl.StartsWith("/")
&& !returnUrl.StartsWith("//") && !returnUrl.StartsWith("/\\"))
{
return Redirect(returnUrl);
}
else
{
return RedirectToAction("Update", "Data");
}
}
else
{
ModelState.AddModelError("", "The user name or password provided is incorrect.");
}
}
// If we got this far, something failed, redisplay form
return View(model);
}
//
// GET: /Account/LogOff
public ActionResult LogOff()
{
FormsAuthentication.SignOut();
return RedirectToAction("Index", "Home");
}
==================================================================================
This is the View code for the Update page (which is the Admin section, and should only be accessible once the user has logged in):
#model DFAccountancy.Models.Data
#{
ViewBag.Title = "Update";
}
<h2>Update</h2>
<script src="#Url.Content("~/Scripts/jquery.validate.min.js")" type="text/javascript"> </script>
<script src="#Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")" type="text/javascript"></script>
<script type="text/javascript">
$(function () { $("#cl_button1").click(function () { $("#para1").val(""); }); });
$(function () { $("#cl_button2").click(function () { $("#para2").val(""); }); });
</script>
#using (Html.BeginForm()) {
#Html.ValidationSummary(true)
<fieldset>
<legend>Data</legend>
<div class="editor-label">
#Html.LabelFor(model => model.para1)
</div>
<div class="editor-field">
#Html.TextAreaFor(model => model.para1, new { cols = 75, #rows = 5 })
#Html.ValidationMessageFor(model => model.para1)
<input id="cl_button1" type="button" value="Clear Paragraph" />
</div>
<div class="editor-label">
#Html.LabelFor(model => model.para2)
</div>
<div class="editor-field">
#Html.TextAreaFor(model => model.para2, new { cols = 75, #rows = 5 })
#Html.ValidationMessageFor(model => model.para2)
<input id="cl_button2" type="button" value="Clear Paragraph" />
</div>
<p>
<input type="submit" value="Update" />
<input type="reset" value="Re-Set to begining" />
</p>
</fieldset>
}
<div>
#Html.ActionLink("Back to List", "Index")
</div>
==================================================================================
This is the Controller code (DataController) that sits behind the Update View page:
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Entity;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using DFAccountancy.Models;
namespace DFAccountancy.Controllers
{
public class DataController : Controller
{
private DataDBContext db = new DataDBContext();
//
// GET: /Data/
public ViewResult Index()
{
return View(db.Data.ToList());
}
//
// GET: /Data/Details/5
public ViewResult Details(string id)
{
Data data = db.Data.Find(id);
return View(data);
}
//
// GET: /Data/Update
public ActionResult Update()
{
var model = db.Data.FirstOrDefault();
return View(model);
}
//
// POST: /Data/Update
[HttpPost]
//[Authorize(Roles = "Administrator")] //Created Validataion so inaccessible from outside
[ValidateInput(false)]
public ActionResult Update(Data data)
{
if (ModelState.IsValid)
{
data.ID = 1; //EF need to know which row to update in the database.
db.Entry(data).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index", "Home");
}
return View(data);
}
}
}
Use the [Authorize] filter. You can apply it to a controller, or to an individual action.
[Authorize]
public class DataController : Controller
{...
Or
[Authorize]
public ActionResult Update()
{...
As a side note, you are not closing your DB connection from what I can see. Your data context needs to have .Dispose() called on it when it is finished.
EDIT
Moreover, it looks like your get method is not decorated with the authorize, which is why anyone can navigate there. Only the post is decorated, or was until commented out, with the authorize filter. [HttpGet] is used for a basic request, whereas the [HttpPost] generally comes from a form post (Sometimes it is done through ajax).