The problem is that if I try and use ValidateAntiForgeryToken to my action return status code 400. Why couldn't find my view page?
Controller:
public IActionResult Edit(int id) {
var model = this.categoryService.GetById(id);
return View(model);
}
[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult Edit(CategoriesViewModel model) {
if (ModelState.IsValid) {
this.categoryService.Update(model);
return RedirectToAction(nameof(Category));
}
return this.View(model.Id);
}
View:
#using CakeStore.App.Areas.Admin.Models.Categories;
#model CategoriesViewModel
#{
ViewData["Title"] = "Edit";
Layout = "~/Areas/Admin/Views/Shared/_AdminLayout.cshtml";
}
<h1 class="text-center text-header-page">Edit Category</h1>
<hr class="hr-admin-divider" />
<form class="mx-auto w-50 form-horizontal" method="post" action="Edit">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-group">
<input asp-for="#Model.Id" type="hidden" name="id" value="#Model.Id"/>
</div>
<div class="form-group">
<label asp-for="#Model.Name"></label>
<input asp-for="#Model.Name" type="text" value="#Model.Name" class="form-control" id="name" name="name">
<span asp-validation-for="#Model.Name" class="text-danger"></span>
</div>
<div class="button-holder d-flex justify-content-center">
<button type="submit" class="btn button-black-white">Edit</button>
</div>
</form>
Model:
public class CategoriesViewModel {
public int Id { get; set; }
[Required]
[StringLength(22,MinimumLength =3,ErrorMessage =AdminConstants.NameRange)]
public string Name { get; set; }
}
You did not inform the form to contain the token. Try this in your form:
<form asp-antiforgery="true" class="mx-auto w-50 form-horizontal" method="post" action="Edit">
NOTE: remember you must use the #addTagHelper directive in order to use "asp-antiforgery"
Related
I'm using a regular html form instead of #html.BeginForm and I have these 2 form tags in my Create.cshtml view file.
I was experimenting with routing, but my post doesn't seem to get the values even if I bind the properties. I've tried in vain but I can't seem to make this work, and can't find the answer from googling.
Create.cshtml
#model Actor
#{
ViewData["Title"] = "Add";
}
<section class="container-xl justify-content-center col-lg-5 col-md-8 col-sm-10">
<div class="row">
<span class="text-center mb-3">
<h5>Add a new record</h5>
</span>
<div>
<form>
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
</form>
<div class="form-group">
<label class="form-label" asp-for="ProfilePictureUrl">Profile Picture</label>
<input class="mb-2 form-control" type="text" asp-for="ProfilePictureUrl" placeholder="Profile picture" />
</div>
<div class="form-group">
<label class="form-label" asp-for="FullName">Full Name</label>
<input class="mb-2 form-control" type="text" placeholder="Full name" asp-for="FullName" />
</div>
<div class="form-group">
<label class="form-label" asp-for="Bio">Biography</label>
<input class="form-control" type="text" placeholder="Bio" asp-for="Bio" />
</div>
<form>
<div class="form-group mt-3">
<a class="btn btn-outline-secondary" asp-action="Index">Show All</a>
<input asp-action="Create2" class="float-end btn btn-outline-success" type="submit" value="Create" />
</div>
</form>
</div>
</div>
</section>
Actor.cs
using System.ComponentModel.DataAnnotations;
namespace MovieProject.Models
{
public class Actor
{
[Key]
public int ActorId { get; set; }
[Display(Name ="Profile Picture")]
public string ProfilePictureUrl { get; set; }
[Display(Name ="Full Name")]
public string FullName { get; set; }
[Display(Name ="Biography")]
public string Bio { get; set; }
}
}
ActorController.cs
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using MovieProject.Data;
using MovieProject.Data.Services;
using MovieProject.Models;
namespace MovieProject.Controllers
{
public class ActorController : Controller
{
private readonly IActorService _service;
public ActorController(IActorService service)
{
_service = service;
}
[HttpPost]
public IActionResult Create2([Bind("ProfilePictureUrl,FullName,Bio")] Actor actorItem)
{
return View("Create");
}
public IActionResult Create()
{
return View();
}
}
}
The methods are getting hit but the post data is null.
Another question is, instead of using MVC convention, can I use a different method name for get and post that is not the same as the view name? How can I get initially load the page for GET using routing that would work in a different view name?
Thanks
can I use a different method name for get and post that is not the
same as the view name?
Yes, you can.
How can I get initially load the page for GET using routing that would
work in a different view name?
return to this view.
public IActionResult Create()
{
return View("aa");
}
Below is a work demo, you can refer to it.
In controller:
public IActionResult Create()
{
return View();
}
[HttpPost]
public IActionResult Create2(Actor actorItem)
{
return View();
}
Create view:
#model nnnn.Models.Actor
#{
ViewData["Title"] = "Create";
}
<h1>Create</h1>
<h4>Actor</h4>
<hr />
<div class="row">
<div class="col-md-4">
<form asp-action="Create2">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-group">
<label asp-for="ActorId" class="control-label"></label>
<input asp-for="ActorId" class="form-control" />
<span asp-validation-for="ActorId" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="ProfilePictureUrl" class="control-label"></label>
<input asp-for="ProfilePictureUrl" class="form-control" />
<span asp-validation-for="ProfilePictureUrl" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="FullName" class="control-label"></label>
<input asp-for="FullName" class="form-control" />
<span asp-validation-for="FullName" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Bio" class="control-label"></label>
<input asp-for="Bio" class="form-control" />
<span asp-validation-for="Bio" class="text-danger"></span>
</div>
<div class="form-group">
<input type="submit" value="Create" class="btn btn-primary" />
</div>
</form>
</div>
</div>
<div>
<a asp-action="Index">Back to List</a>
</div>
#section Scripts {
#{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}
result:
I have simple view model:
public class GogViewModel
{
[Required]
public string Name { get; set; }
public bool IsBeta { get; set; }
public string Result { get; set; }
}
and controller:
[HttpGet]
public IActionResult Index()
{
return View(new GogViewModel());
}
[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult Index(GogViewModel model)
{
if (ModelState.IsValid)
{
model.Result = DoSomething();
}
return View(model);
}
and View:
#model GogViewModel
<div>
<form method="post">
#Html.AntiForgeryToken()
<div class="form-group row">
<label class="col-sm-2 col-form-label" asp-for="Name">Release name</label>
<div class="col-sm-10">
<input type="text" class="form-control" asp-for="Name" placeholder="paste release name here" />
</div>
</div>
<div class="form-group row">
<label class="col-sm-2 col-form-label" asp-for="Result">Result</label>
<div class="col-sm-9">
<input type="text" class="form-control-plaintext" readonly="readonly" asp-for="Result" />
<p>#Model.Result</p> <!-- it works here means the value is printed -->
</div>
<div class="col-sm-1">
<button type="button" class="btn btn-outline-info copy float-right" data-target="Result"><i class="far fa-copy"></i></button>
</div>
<div class="clearfix"></div>
</div>
<input type="submit" class="btn btn-success" value="Fix it" />
</form>
</div>
Somehow the result is not output after POST request.
Why ? Where's my mistake ?
It will work in the following way:
Set private setter to Result property => public string Result {get; private set;}
Add method which set value for Result: internal void SetResult(string result) { Result = result; }
In controller, call SetResult like: model.SetResult(...);
and will display in readonly input. I don't know why it works and I don't understand why the value is cleared when put it on input.
I used input with readonly attribute in order to copy to clipboard the output value with javascript.
maybe you need to add something in the input tag like value="#Model.Result"?
I have a page containing one main form and multiple smaller duplicate forms. I am trying to post them both using one button with JQuery, and handle them in the order of: main form first then smaller forms second. However, when I attempt to access the smaller forms, I am getting data from only the last one. I was thinking that the best way to do it would be to put the smaller forms into a list, pass that to my handler, and then do a foreach loop through the list. How would I put the form information into a list before my handler uses it?
Here is my model:
public class SubmitModel
{
[BindProperty]
public Timelineinfo Timelineinfo { get; set; } //main form
[BindProperty]
public Media Medias { get; set; }//small form
}
My controller:
[HttpPost]
[Route("/Submit/Submit")]
[ValidateAntiForgeryToken] //Main form handler
public async Task<IActionResult> Submit(SubmitModel model)
{
if (ModelState.IsValid)
{
//Removed for brevity, but
//Fill out some more values in main form then submit main form to Database,
}
return View("Pages/Submit.cshtml", model);
}
[Route("/Submit/MediaAdd")]
[ValidateAntiForgeryToken] //Add media form handler
public async Task<IActionResult> MediaAdd(SubmitModel model)
{
if (ModelState.IsValid)
{
//Removed for brevity, but
//Fill out some more values in small form then submit small form to Database
}
return View("Pages/Submit.cshtml", model);
}
And a truncated version of my small form (main form looks similar but with with Submit instead of MediaAdd:
#using (Html.BeginForm("MediaAdd", "Timeline", FormMethod.Post))
{
#Html.AntiForgeryToken()
<div class="container">
<div class="form-row">
<label asp-for="Medias.Blurb" class="control-label">Blurb<span class="text-danger ml-1">*</span> <span class="text-muted ml-1">Explain what is in the media</span></label>
<input asp-for="Medias.Blurb" class="form-control" />
<span asp-validation-for="Medias.Blurb" class="text-danger"></span>
</div>
<div class="form-row">
<div class="ml-3 col-4">
<label class="form-check-label">Is there any blood or gore in the video?<span class="text-danger ml-1">*</span></label>
<div class="form-check m-2 d-inline">
<input type="radio" asp-for="Medias.Gore" class="form-check-input" value="0">
<label class="form-check-label">No</label>
</div>
<div class="form-check m-2 d-inline">
<input type="radio" asp-for="Medias.Gore" class="form-check-input" value="1" />
<label class="form-check-label">Yes</label>
<span asp-validation-for="Medias.Gore" class="text-danger"></span>
</div>
</div>
</div>
</div>
}
Finally, how I am currently submitting my forms:
$('#submitButton').click(function () {
$('form').submit();
});
I made an example based on your requirement, you may refer to it:
Model:
public class SubmitModel
{
[BindProperty]
public Timelineinfo Timelineinfo { get; set; }
[BindProperty]
public List<Media> Medias { get; set; }
}
public class Timelineinfo
{
public int TimeId { get; set; }
public string Description { get; set; }
}
public class Media
{
public string Blurb { get; set; }
public string Gore { get; set; }
}
Main form View(Index):
#model SubmitModel
<button id="AddMedia" type="button" class="btn btn-primary">Add Media</button>
#using (Html.BeginForm("MediaAdd", "Timeline", FormMethod.Post))
{
<div class="container">
<div class="form-row">
<label asp-for="Timelineinfo.TimeId" class="control-label">TimeId</label>
<input asp-for="Timelineinfo.TimeId" class="form-control" />
</div>
<div class="form-row">
<label asp-for="Timelineinfo.Description" class="control-label">Description</label>
<input asp-for="Timelineinfo.Description" class="form-control" />
</div>
</div>
<div id="subformsArea">
</div>
<div>
<input type="submit" value="submit" class="btn btn-primary" />
</div>
}
#section scripts{
<script>
var count = 0;
$("#AddMedia").on('click', function () {
$.ajax({
url: '/Timeline/Media',
method: 'get',
data: {data : count},
success: function (result) {
$("#subformsArea").append(result);
count++;
}
})
})
</script>
}
Subform View(Partial View):
#model SubmitModel
#{
int i = ViewBag.Count;
}
<div class="container">
<div class="form-row">
<label asp-for="Medias[i].Blurb" class="control-label">Blurb<span class="text-danger ml-1">*</span> <span class="text-muted ml-1">Explain what is in the media</span></label>
<input asp-for="Medias[i].Blurb" class="form-control" />
<span asp-validation-for="Medias[i].Blurb" class="text-danger"></span>
</div>
<div class="form-row">
<div class="ml-3 col-4">
<label class="form-check-label">Is there any blood or gore in the video?<span class="text-danger ml-1">*</span></label>
<br />
<div class="form-check m-2 d-inline">
<input type="radio" asp-for="Medias[i].Gore" class="form-check-input" value="0">
<label class="form-check-label">No</label>
</div>
<div class="form-check m-2 d-inline">
<input type="radio" asp-for="Medias[i].Gore" class="form-check-input" value="1" />
<label class="form-check-label">Yes</label>
<span asp-validation-for="Medias[i].Gore" class="text-danger"></span>
</div>
</div>
</div>
</div>
Controller:
public class TimelineController : Controller
{
public IActionResult Index()
{
return View();
}
[Route("/Submit/MediaAdd")]
[ValidateAntiForgeryToken] //Add media form handler
public IActionResult MediaAdd(SubmitModel model)
{
if (ModelState.IsValid)
{
//Removed for brevity, but
//Fill out some more values in small form then submit small form to Database
}
return View("Pages/Submit.cshtml", model);
}
public IActionResult Media(int data)
{
ViewBag.Count = data;
return PartialView();
}
}
Result:
This is how I would do it. The simpler the better:
<form action='myAction' method='postOrGet'>
//subform 1
<input name='inputA' subform='s1'.../>
//subform 2
<input name='inputX' subform='s2'.../>
...
<button type='submit'>submit</button>
</form>
Then you can easily separate submitted data based on the "subform" attribute (s1, s2. etc)
Is my ViewModel flow right? The 'SubObjectViewModel' class usage is a bad or a good practice? Or should I try other option, like creating a ViewModel only to this class?
And also, how should I return the ViewModel 'ObjectViewModel' to the controller with all its values, change them and refresh values from page view?
My ViewModel
Public class ObjectViewModel{
public string name { get; set; }
public int value { get; set; }
public bool IsCameraPermited { get; set; }
public List<SubObjectViewModel> choosenSubObjects{ get; set; } // need to get it back on controller;
public class SubObjectViewModel
{
public int IdObject { get; set; }
public string Name { get; set; }
public string Config { get; set; }
}
public List<Object> listSub{ get; set; } //list that will be filled on 'Create' Controller
}
My Controller
public IActionResult Create(int id)
{
List<Object> listSubObject = new List<Object>();
listSubObject = _getAllSubObjectsDAO.ListByID(id);
List<Object> choosenObjects= new List<Object>();
choosenObjects = _getChoosenObjectsDAO.ListByID(id);
List<SubObjectViewModel> listSubObject = new List<SubObjectViewModel>();
foreach (Object item in choosenObjects )
{
string config = _configurationDAO.GetConfigurationById(item.configId);
ObjectViewModel .SubObjectViewModel SubObject = new ObjectViewModel .SubObjectViewModel { IdObject = item.Id, Name = item.Name , Config = config };
listSubObject.Add(setorVM);
}
ObjectViewModel objVM = ObjectViewModel{
name ="test",
value = 2,
IsCameraPermited =true,
listSub = listSubObject,
choosenSubObjects = listSubObject
};
return View(objVM);
}
My View
#model Project.Models.ViewModels.ObjectViewModel
... more code
<form asp-action="Create">
<div class="row">
<div class="col-sm-6 ctnFullHeight">
<div class="shadowBoxForm formCreateLeft position-absolute col-sm-11 ctnFullHeight">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-group">
<input asp-for="name " class="form-control" />
<span asp-validation-for="name " class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="IsCameraPermited" class="control-label"></label>
<input asp-for="IsCameraPermited" type="checkbox" />
</div>
<div class="form-group float-right position-relative">
<input type="submit" value="Save" class="btn btn-default" />
</div>
</div>
</div>
<div class="col-sm-6">
<div class="shadowBoxForm formCreateLeft position-absolute col-sm-11 ">
#foreach (var item in listSub)
{
<div class="txt">
#item
</div>
}
</div>
</div>
</div>
</form>
Thanks in advance.
how should I return the ViewModel 'ObjectViewModel' to the controller with all its values
To achieve above requirement, you can refer to the following code snippet.
#model ObjectViewModel
#{
ViewData["Title"] = "Create";
var choosenSubObjects = Model.choosenSubObjects;
}
<form method="post">
<div class="row">
<div class="col-sm-6 ctnFullHeight">
<div class="shadowBoxForm formCreateLeft position-absolute col-sm-11 ctnFullHeight">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-group">
<input asp-for="name " class="form-control" />
<span asp-validation-for="name " class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="IsCameraPermited" class="control-label"></label>
<input asp-for="IsCameraPermited" type="checkbox" />
</div>
<div class="form-group float-right position-relative">
<input type="submit" value="Save" class="btn btn-default" />
</div>
</div>
</div>
<div class="col-sm-6">
<div class="shadowBoxForm formCreateLeft position-absolute col-sm-11 ">
#for (var i = 0; i < Model.choosenSubObjects.Count; i++)
{
<div class="txt">
<div class="form-group">
<label asp-for="#choosenSubObjects[i].IdObject" class="control-label"></label>
<input asp-for="#choosenSubObjects[i].IdObject" type="text" />
</div>
<div class="form-group">
<label asp-for="#choosenSubObjects[i].Name" class="control-label"></label>
<input asp-for="#choosenSubObjects[i].Name" type="text" />
</div>
<div class="form-group">
<label asp-for="#choosenSubObjects[i].Config" class="control-label"></label>
<input asp-for="#choosenSubObjects[i].Config" type="text" />
</div>
</div>
}
</div>
</div>
</div>
</form>
Controller Actions
[HttpGet]
public IActionResult Create(int id)
{
//code logic here
//...
return View(objVM);
}
[HttpPost]
public IActionResult Create(ObjectViewModel objectViewModel)
{
//code logic here
//...
Test Result
Have a partial view containing a form, but can't seem to get validation errors to show up.
Models
public class HomeModel
{
public LoginModel LoginModel { get; set; }
}
public class LoginModel
{
[Required]
[Display(Name = "Email")]
public string Username { get; set; }
[Required]
public string Password { get; set; }
}
Index.cshtml:
#model MyApp.Models.HomeModel
#{
await Html.RenderPartialAsync("_LoginPartial", Model.LoginModel);
}
HomeController
public IActionResult Index()
{
return View(new HomeModel()
{
LoginModel = new LoginModel()
});
}
[HttpPost]
public IActionResult Login(LoginModel model)
{
return View("Index", new HomeModel()
{
LoginModel = model
});
}
_LoginPartial
#model MyApp.Models.LoginModel
<form asp-action="Login" asp-controller="Home" method="post" role="form" style="width:70%;margin: 0 auto;">
<div style="display:none" asp-validation-summary="ValidationSummary.All" class="alert alert-danger fade in">
×
<strong>Error!</strong> A problem has been occurred while submitting your data.
</div>
<div class="form-group" style="text-align:left">
<label asp-for="Username" style="font-size:22px;" class="control-label"></label>
<input asp-for="Username" type="text" class="form-control" />
</div>
<div class="form-group" style="text-align:left">
<label asp-for="Password" style="font-size:22px;" class="control-label"></label>
<input asp-for="Password" type="password" class="form-control" />
</div>
<div class="spacer-15"></div>
<div class="form-group" style="font-size:22px;">
<button type="submit"
style="background-color: Transparent;
background-repeat:no-repeat;
border: none;
cursor:pointer;
overflow: hidden;
outline:none;" value="Login">
<img src="~/images/flower.png" />Next
</button>
</div>
</form>
It looks like you're missing a few things. Each of your inputs will need a corresponding validation element that uses asp-validation-for.
<div class="form-group" style="text-align:left">
<label asp-for="Username" style="font-size:22px;" class="control-label"></label>
<input asp-for="Username" type="text" class="form-control" />
<span asp-validation-for="Username" class="text-danger"></span>
</div>
Likewise, if you're planning on using jQuery validation, you should add the following to the environment elements in your _Layout.cshtml file.
<script src="~/lib/jquery-validation/dist/jquery.validate.js"></script>
<script src="~/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.js"></script>