Using remote attribute in .net core razor pages. The Parameter does not get value in Action Method of Controller - validation

I am using Remote Attribute validation
The method is invoked successfully on textchange event. However, the parameters in the action method of the controller does not get the value of the field.
Here is the Action Method in the HomeController.cs. When invoked the Name parameter remains null. I will be pleased if someone solve this problem
[AcceptVerbs("Get", "Post")]
public async Task<ActionResult> IsExist(string Name)
{
List<Keywords> keywords = new List<Keywords>();
HttpClient client = _api.Initial();
HttpResponseMessage res = await client.GetAsync("api/Keywords");
if (res.IsSuccessStatusCode)
{
var result = res.Content.ReadAsStringAsync().Result;
keywords = JsonConvert.DeserializeObject<List<Keywords>>(result);
}
if (keywords.FirstOrDefault(x => x.Name == Name) == null)
{
return Json(false);
}
else
{
return Json(true);
}}
Here is the Model
public partial class Keywords
{
public int Id { get; set; }
[Display(Name = "Name of Keyword")]
[Required]
[Remote(action: "IsExist",controller: "Home", ErrorMessage = "Keyword already present!")]
public string Name { get; set; }}
Here is the razor page in which I want to implement validation
#page
#model Consumer.Pages.Keyword.CreateModel
#{
ViewData["Title"] = "Create";
}
<h1>Create</h1>
<h4>Keywords</h4>
<hr />
<div class="row">
<div class="col-md-4">
<form method="post">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-group">
<label asp-for="Keywords.Name" class="control-label"></label>
<input asp-for="Keywords.Name" class="form-control" />
<span asp-validation-for="Keywords.Name" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Keywords.Department" class="control-label"></label>
<select asp-for="Keywords.DepartmentId" class="form-control" asp-items="ViewBag.Department"></select>
</div>
<div class="form-group">
<input type="submit" value="Create" class="btn btn-primary" />
</div>
</form>
</div>
</div>
<div>
<a asp-page="Index">Back to List</a>
</div>
#section Scripts {
#{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}

I found the solution. It is to
Remove partial in the model class definition.
Replace in the Razor Page
<input asp-for="Keywords.Name" class="form-control" />
with
<input asp-for="Keywords.Name" name="Name" class="form-control" />

The [Remote] attribute is all but useless. There's a long-standing problem from the ASP.NET MVC days that migrated its way into ASP.NET Core unabated, as well. Namely, the action that handles the remote must take a param that matches the input name of what what's being validated. In other words, your action takes a param, name, but what's being sent to it is Keywords.Name, which cannot bind to name. You'd have to do something like:
public async Task<ActionResult> IsExist(Keywords keywords)
And then, then the value will be available via keywords.Name. This obviously makes the usage highly dependent on the particular implementation, so if there's a situation with a different input name like Foo.Keywords.Name, then you'd need an entirely different action, to match that binding, and basically duplicate the logic (though you could factor out the majority the logic into some common method all the actions could utilize).
Long and short, you're probably better off just handling this yourself manually. Just bind to the input event on the name input, and then call your method via AJAX. Then you can explicitly control the key that's sent in the request body, such that it will always match your action. Just note that you'll also want to debounce sending that AJAX request so that it only happens every few keystrokes or so. Otherwise, you're going to hammer your action.

Related

Why has my unobtrusive ajax stopped updating my partial view in ASP.NET Core 3.1?

I'm using unobtrusive ajax inside a kendo window to update a form once it's been filled in. The form should update the Id's of the DOM elements dynamically and also show the relative data inside the form as returned from the controller.
Let's start, here is the window element that houses the partial view which is a form:
#model Requirement
#{
int localId = Model.Id;
}
<div class="window-content">
<form method="post" data-ajax-url="/Home/Process_Requirement" data-ajax="true" data-ajax-method="post" data-ajax-loading="#spinner" data-ajax-update="#update-form-requirement" data-ajax-success="form_requirement_success">
<div id="update-form-requirement">
<partial name="_Form_Requirement" />
</div><!--/update-panel-->
<div class="window-footer">
<div class="container">
<div class="row no-gutters">
<div class="col">
<button type="submit" class="input-submit float-right">Save</button>
</div>
</div>
</div>
</div>
</form>
</div>
_Form_Requirement
#model Requirement
#{
int localId = Model.Id;
}
<div class="tab-content">
<div class="tab-pane fade show active" id="form-requirement-basic-#localId" role="tabpanel" aria-labelledby="requirement-tab">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<input type="text" id="Id_#localId" name="Id" asp-for="Id" readonly />
<input type="text" id="CreatedUser_#localId" asp-for="CreatedUser" readonly />
<div class="container">
<div class="row">
<div class="col">
<div class="form-group">
<label>Vessel Type</label>
<kendo-dropdownlist name="VesselType"
for="VesselType"
id="VesselType_#localId"
datatextfield="TypeName"
datavaluefield="Id"
min-length="3"
style="width: 100%"
value-primitive="true"
option-label="Select vessel type"
footer-template="<button class='dropdown-button k-icon k-i-plus-outline' data-object-title='Add vessel type' data-object-function='_Window_Vessel_Type' onclick='open_data_window(this, event)'></button>"
filter="FilterType.Contains">
<datasource type="DataSourceTagHelperType.Ajax" page-size="80">
<transport>
<read url="/Vessel/ReadVesselTypes" />
</transport>
</datasource>
<popup-animation>
<open duration="300" effects="fadeIn" />
<close duration="300" effects="fadeOut" />
</popup-animation>
</kendo-dropdownlist>
</div>
</div>
</div>
</div>
</div>
</div>
When the form is submitted the controller action does the following:
/Home/Process_Requirement
public IActionResult Process_Requirement(Requirement model)
{
//Define the partial view we want
string modal = "_Form_Requirement";
//If the Id is 0 then create a new requirement
if (model.Id == 0)
{
if (ModelState.IsValid)
{
try {
_requirementService.InsertRequirement(model);
var getData = _requirementService.GetRequirement(model.Id);
return PartialView(modal, getData);
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
}
else
{
try
{
_requirementService.UpdateRequirement(model);
return PartialView(modal, model);
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
return PartialView(modal);
}
So, when you first open a window to create a new requirement the localId is 0 because you're not editing one, that's expected behavior.
When you then fill in all the details and submit the form, the data is posted to the database and saved correctly but the model doesn't seem to update with the new data, for example the input for the Id remains as 0:
<input type="text" id="Id_#localId" name="Id" asp-for="Id" readonly />
yet the id for the input updates to the newly created id number. Why is this happening and can I do anything about it?
you have a bug. fix the last return
return PartialView(modal,model);
Is the Id primary key? If so, it is not recommend to change the primary key, you should add a new column to be the key.
the data is posted to the database and saved correctly but the model doesn't seem to update with the new data
This could be caused by the concurrency issues. I can't see your update code now, but you can
try to change your update code like this:
public async Task<IActionResult> Process_Requirement(Requirement model)
{
string modal = "_Form_Requirement";
if (model.Id == 0)
{
//....
}
else //to update
{
await TryUpdateModelAsync<Requirement>(_context.Requirements.FirstOrDefault(x => x.RId ==model.RId),"",c => c.Id, c => c.CreatedUser);
_context.SaveChanges();
return PartialView(modal, model);
}
return PartialView(modal);
}
My Model:
public class Requirement
{
[Key]
public int RId { get; set; } //new column
public int Id { get; set; }
public string CreatedUser { get; set; }
}
And in partial, pass the key with a hidden field:
<input type="text" asp-for="RId" hidden/>
<input type="text" id="Id_#localId" name="Id" asp-for="Id" />
<input type="text" id="CreatedUser_#localId" asp-for="CreatedUser" />
Result:

Spring Web application tymeleaf : POST request directed to wrong address

I have post mapping for URL: "/bank/addnew" My controller looks like:
Upon submission the form is submitted to "http://127.0.0.1:8082/banks/%20/banks" However, I need it to go to "http://127.0.0.1:8082/banks/addnew". Thanks for the Help!
#Controller
public class BankController {
#Autowired private BankService bankService;
#GetMapping("/banks")
public String bankList() {
return "bank/bank_list";
}
#PostMapping(value="/banks/addnew")
public String addNew(Bank bank) {
bankService.save(bank);
return "redirect: /banks";
}
}
And my template:
<form method="POST" action="#" th:action="#{/banks/addnew}" >
<div class="form-group">
<label for="recipient-name" class="col-form-label">Bank Name:</label>
<input type="text" class="form-control" id="recipient-name" name="name">
</div>
<div class="form-group">
<button type="submit" class="btn btn-primary">Submit</button>
</div>
</form>
The Post Method was actually working okay. I checked it by printing the Model object inside the target controller. However, it was being submitted to the database a NULL value because I didn't initialize getters and setters for my Model. Finally, there was a space in the return string, thus I removed it.
The controller Should have a return statement with no space.
return "redirect:/banks";

Razor Pages Net Core auto reload partial view on set frequency

I am still trying to get to grips with Razor Pages for Net Core and seem to be a bit stuck on this. I have my Index.cshtml:
#page
#model IndexModel
<input type="hidden" name="hdnPageSelector" id="hdnIndexPage" />
<div class="text-center">
<p>Welcome to</p>
<h1 class="display-4">"My Web App"</h1>
</div>
<div class="form-row">
<div class="form-group col-md-2">
<partial name="IndexPartials/_Navigation" />
</div>
<div class="form-group col-md-1">
</div>
<div class="form-group col-md-6">
<partial name="IndexPartials/_Body" />
</div>
<div class="form-group col-md-1">
</div>
<div id="refreshMembers" class="form-group col-md-2">
<partial name="IndexPartials/_Members" />
</div>
</div>
Note the last div has an id="refreshMembers".
The partial view (_Members) that is loaded there looks like this:
#model IndexModel
<label>Members</label>
<br />
#{
foreach (ApplicationUser user in Model.AppUsersList)
{
if (user.IsLoggedIn)
{
<label>#user.FirstName #user.LastName </label>
<span class="dot"></span>
}
else
{
<label>#user.FirstName #user.LastName</label>
}
}
}
Within the controller I have a property called:
public IList<ApplicationUser> AppUsersList { get; set; }
And this is populated on OnGetAsync() as follows:
AppUsersList = _userManager.Users.OrderBy(x => x.FirstName).Where(y => y.UserName != currentUser.UserName).ToList();
This is fine, the page loads with the partial view populated as expected. I now want the partial to refresh every 5 seconds so I have put this piece of Javascript/JQuery in place:
$(function () {
setInterval(function () {
$("#refreshMembers").load("/Index?handler=RefreshMembers");
}, 5000);
});
with the following method setup:
public async Task<IActionResult> OnGetRefreshMembers()
{
var currentUser = await _userManager.GetUserAsync(User);
AppUsersList = _userManager.Users.OrderBy(x => x.FirstName).Where(y => y.UserName != currentUser.UserName).ToList();
return new PartialViewResult
{
ViewName = "_Members",
ViewData = new ViewDataDictionary<List<ApplicationUser>>(ViewData, AppUsersList)
};
}
However the partial view doesn't get refreshed. If I put a breakpoint within this method I can see it is being hit every 5 seconds, despite Devtools stating there is an error on each attempt:
In a nut shell, I just can't seem to get my partial view to be reloaded every 5 seconds. It feels like I am close but just missing something and don't know what that is.
Having been reminded to check the Output window in VS a bit better, I found the cause of my problems... Well two things actually. This is the corrected method:
public async Task<IActionResult> OnGetRefreshMembers()
{
var currentUser = await _userManager.GetUserAsync(User);
AppUsersList = _userManager.Users.OrderBy(x => x.FirstName).Where(y => y.UserName != currentUser.UserName).ToList();
return new PartialViewResult
{
ViewName = "IndexPartials/_Members",
ViewData = new ViewDataDictionary<IndexModel>(ViewData, this)
};
}
Where...
I didn't include the folder that the partial lives in when naming it on the PartialViewResult
I need to return the entire IndexModel object - having updated the AppUserList property, and not just the list of AppUsers.

how to resolve Request method 'GET' not supported

I am getting below warning as
WARN [org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver] (default task-1) Resolved [org.springframework.web.HttpRequestMethodNotSupportedException: Request method 'GET' not supported]
I have already set the method as POST but still I'm getting the above error. I'm getting this warning message for my delete controller all other CRUD operations are working fine, except delete.
Please find below code
Controller mapped deleteproducts :
#RequestMapping(value="/deleteproducts", method= RequestMethod.POST)
public String deleteProduct(#PathVariable("productId")int productId) {
IProductsDAO ip = new ProductsDAOImpl();
boolean b = ip.deleteProduct(productId);
if(b)
return "success";
else
return "deleteproducts";
here's my jsp view:
<body>
<form id="update product form" action="${pageContext.request.contextPath}/deleteproducts" method="post" role="form" style="display: none;">
<div class="form-group row">
<label for="product Id" class="col-sm-2 col-form-label">Id</label>
<div class="col-sm-10">
<input type="text" name="productId" class="form-control" id="productid" placeholder="Enter the product Id you want to delete">
</div>
</div>
</form>
</body>
DAOimplementation for delete Method call:
public boolean deleteProduct(int productId)
{
boolean b = true;
try
{
sess.beginTransaction();
Products p = (Products)sess.load(Products.class, new Integer(productId));
sess.delete(p);
sess.getTransaction().commit();
}catch(Exception ex)
{
sess.getTransaction().rollback();
b = false;
}
return b;
}
can some one now tell me what changes should I make in my code to fix this ?
Thank you!
edit 1:
#DeleteMapping(value="/deleteproducts/{productId}")
public String deleteProduct(#PathVariable("productId")int productId) {
IProductsDAO ip = new ProductsDAOImpl();
boolean b = ip.deleteProduct(productId);
if(b)
return "success";
else
return "deleteproducts";
}
still getting a warning as:
WARN [org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver] (default task-1) Resolved [org.springframework.web.HttpRequestMethodNotSupportedException: Request method 'GET' not supported]
I don't understand #RequestMapping(value="/deleteproducts", method= RequestMethod.POST)
what do you mean by this? You are making a RequestMapping want to delete a record and method is POST?
I would suggest please follow the standard way of development. If you want to delete DeleteMapping, for POST use PostMapping and to retrieve some information you can use GetMapping.
Ideally, it should be
#DeleteMapping("/deleteproducts/{id}")
public void deleteStudent(#PathVariable long id) {
deleteproductsRepository.deleteById(id); or some CRUD logic to delete
}
You can refer to this link for better understanding REST
AS i think request form going to GET Method you can try javascript to submit form with function call.
Please find below code :
<form id="update product form" action="${pageContext.request.contextPath}/deleteproducts" method="POST">
<div class="form-group row">
<label for="product Id" class="col-sm-2 col-form-label">Id</label>
<div class="col-sm-10">
<input type="text" name="productId" class="form-control" id="productid" placeholder="Enter the product Id you want to delete">
</div>
<div class="col-sm-10">
<input type="button" value="submit" onclick="javascript:formSubmit()" name="submit" ></a>
</div>
</div>
</form>
<script>
function formSubmit() {
if(!isEmpty(document.from.productId.value)){ //even you can validate values in productId
document.form.method='POST';
document.form.action='/deleteproducts';
document.form.submit();
}
)
<script>
After writing the below code
#DeleteMapping(value="/deleteproducts/{productId}")
public String deleteProduct(#PathVariable("productId")int productId) {
IProductsDAO ip = new ProductsDAOImpl();
boolean b = ip.deleteProduct(productId);
if(b)
return "success";
else
return "deleteproducts";
}
After this instead of running on a normal browser, try running it on a REST API. I tried it on POSTMAN API and I don't get the error. Look at the below image

MVC3 - Understanding POST with a button

How does one obtain the form data after submitting it?
<form target="_self" runat="server">
<p>
<select id="BLAHBLAH2">
<option>2010</option>
<option>2011</option>
<option>2012</option>
<option>2013</option>
</select>
<input type="submit" runat="server" value="Change Year" />
</p>
</form>
This hits the controller's Index method. But, there's nothing in Request.Form. Why?
Second, can I use
<input type="button" instead of type=submit? That is, without introducing ajax via onclick.
Finally, how do I submit to a different method in the controller, e.g. Create?
Try removing those runat server tags. They should not be used in ASP.NET MVC. Also your select doesn't have a name. If an input element doesn't have a name it won't submit anything. Also your option tags must have value attributes which indicates what value will be sent to the server if this options is selected:
<form action="/Home/Create" method="post">
<p>
<select id="BLAHBLAH2" name="BLAHBLAH2">
<option value="2010">2010</option>
<option value="2011">2011</option>
<option value="2012">2012</option>
<option value="2013">2013</option>
</select>
<input type="submit" value="Change Year" />
</p>
</form>
But the correct way to generate forms in ASP.NET MVC is to use HTML helpers. Depending on the view engine you are using the syntax might be different. Here's an example with the Razor view engine:
#model MyViewModel
#using (Html.BeginForm("Create", "Home"))
{
<p>
#Html.DropDownListFor(x => x.SelectedYear, Model.Years)
<input type="submit" value="Change Year" />
</p>
}
Here you have a strongly typed view to some given view model:
public class MyViewModel
{
public string SelectedYear { get; set; }
public IEnumerable<SelectListItem> Years
{
get
{
return Enumerable
.Range(2010, 4)
.Select(x => new SelectListItem
{
Value = x.ToString(),
Text = x.ToString()
});
}
}
}
which is populated by some controller action that will render this view:
public class HomeController: Controller
{
public ActionResult Index()
{
var model = new MyViewModel();
return View(model);
}
[HttpPost]
public ActionResult Create(MyViewModel model)
{
... model.SelectedYear will contain the selected year
}
}
None of your <option> tags have a value:
...
<option value="2010">2010</option>
...
As noted by David, runat="server" is most definitely a WebForms thing, so you can 86 that.
If you want to submit to a different method on your controller you just need to specify the URL for that method.
Easy way using Html.BeginForm:
#using (Html.BeginForm("AnotherAction", "ControllerName")) {
<!-- Your magic form here -->
}
Using Url.Action
<form action="#Url.Action("AnotherAction")" method="POST">
<!-- Your magic form here -->
</form>
You can also use
In Controller
int Value = Convert.ToInt32(Request["BLAHBLAH2"]); //To retrieve this int value
In .cshtml file use
<select id="IDxxx" name="BLAHBLAH2">
//Request[""] will retrieve the VALUE for the html object ,whose "name" you request.

Resources