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

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:

Related

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.

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

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.

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

Model Binding collections in ASP.NET MVC with hidden controls

Hihi,
Having trouble model binding with this class (the following concrete, not the abstract).
Ignore the basic properties, its the Lists that I'm interested in binding.
public abstract class MessageModel
{
public string Tag { get; set; }
public string Message { get; set; }
public int Id { get; set; }
public const int DefaultIdValue = Int32.MinValue;
public List<LinkModel> Linked { get; set; }
public List<LinkModel> NotLinked { get; set; }
protected MessageModel()
{
Id = DefaultIdValue;
Linked = new List<LinkModel>();
NotLinked = new List<LinkModel>();
}
protected MessageModel(string tag, string message):this()
{
Tag = tag;
Message = message;
}
}
public class TextModel:MessageModel
{
public int TextId { get; set; }
public TextModel()
{
TextId = DefaultIdValue;
}
}
This is the submission I get on the server side on submit (formatted for sanity):
Tag=
&Message=
&NotLinked.index=35fda83a053645e6809bbb8b0ea00103
&NotLinked.index=14c2e286e28b4c9d8f889fb3eb437e5f
&NotLinked.%5b35fda83a053645e6809bbb8b0ea00103%5d.RecipientId=1
&NotLinked.%5b35fda83a053645e6809bbb8b0ea00103%5d.RecipientName=Bob+Biggins
&NotLinked.%5b14c2e286e28b4c9d8f889fb3eb437e5f%5d.RecipientId=2
&NotLinked.%5b14c2e286e28b4c9d8f889fb3eb437e5f%5d.RecipientName=Billy+Oswold
&Submit=Submit
When the function is called that accepts that model the NotLinked collection is set to null. D:
The (relevant) output html looks like this (im trying to "faux" bind to:
<ol> and <li>
with jQuery doing the work of moving stuff around)
<div id="NotLinkedContainer">
<ol id="NotLinked" name="NotLinked" style="width: 500px;height: 200px">
<li value="1">Bob Biggins
<input id="NotLinked_index" name="NotLinked.index" type="hidden" value="a0ab331bee2a461084b686e13a87090b" />
<input id="NotLinked__a0ab331bee2a461084b686e13a87090b__RecipientId" name="NotLinked.[a0ab331bee2a461084b686e13a87090b].RecipientId" type="hidden" value="1" />
<input id="NotLinked__a0ab331bee2a461084b686e13a87090b__RecipientName" name="NotLinked.[a0ab331bee2a461084b686e13a87090b].RecipientName" type="hidden" value="Bob Biggins" />
</li>
<li value="2">Billy Oswold
<input id="NotLinked_index" name="NotLinked.index" type="hidden" value="d7d294d3174c4bd98d583e92010359e7" />
<input id="NotLinked__d7d294d3174c4bd98d583e92010359e7__RecipientId" name="NotLinked.[d7d294d3174c4bd98d583e92010359e7].RecipientId" type="hidden" value="2" />
<input id="NotLinked__d7d294d3174c4bd98d583e92010359e7__RecipientName" name="NotLinked.[d7d294d3174c4bd98d583e92010359e7].RecipientName" type="hidden" value="Billy Oswold" />
</li>
</ol>
</div>
Any ideas? Haven't done this sort of complex binding before so I'm at a loss at the simple mistake I've probably made.
Try removing the dot notation before the brackets and since this isn't a dictionary, but a list, you need to use indices, not keys. The correct syntax for a list in a razor view should look more like this:
<div id="NotLinkedContainer">
<ol id="NotLinked" name="NotLinked" style="width: 500px;height: 200px">
<li value="1">Bob Biggins
<input id="NotLinked_index" name="NotLinked.index" type="hidden" value="a0ab331bee2a461084b686e13a87090b" />
<input id="NotLinked__a0ab331bee2a461084b686e13a87090b__RecipientId" name="NotLinked[0].RecipientId" type="hidden" value="1" />
<input id="NotLinked__a0ab331bee2a461084b686e13a87090b__RecipientName" name="NotLinked[0].RecipientName" type="hidden" value="Bob Biggins" />
</li>
<li value="2">Billy Oswold
<input id="NotLinked_index" name="NotLinked.index" type="hidden" value="d7d294d3174c4bd98d583e92010359e7" />
<input id="NotLinked__d7d294d3174c4bd98d583e92010359e7__RecipientId" name="NotLinked[1].RecipientId" type="hidden" value="2" />
<input id="NotLinked__d7d294d3174c4bd98d583e92010359e7__RecipientName" name="NotLinked[1].RecipientName" type="hidden" value="Billy Oswold" />
</li>
</ol>
</div>
Here is an article that covers what you are attempting to do:
http://haacked.com/archive/2008/10/23/model-binding-to-a-list.aspx

Unobtrusive validation editorfor

I've got a partial View, loaded from an Action, so the parent view contains:
#Html.Action("TourSearch")
The TourSearch View uses a editor as such:
#Html.EditorFor(model => model.ImpersonatedAgentModel, "ImpersonatedAgentView")
where ImpersonatedAgentModel is as such:
[Serializable]
public class ImpersonatedAgentModel
{
[Required(ErrorMessage = "Please provide a User ref")]
public string AgentImpersonatedUserName { get; set; }
[Required(ErrorMessage="Please provide a ABTA/AgencyCode")]
public string AgentImpersonatedBranchCode { get; set; }
[Required(ErrorMessage = "Please provide a User ref")]
public int? AgentImpersonatedBranchID { get; set; }
}
My editor is pretty straight forward:
#model Travel2.WebUI.Models.ImpersonatedAgentModel
<ul id="agencyDetails">
<li>
<label for="AgentImpersonatedBranchCode">ABTA/Agency Code: *</label>
#Html.TextBoxFor(model => model.AgentImpersonatedBranchCode, new {ID="txtBranchCode" })
#Html.ValidationMessageFor(model => model.AgentImpersonatedBranchCode, "*")
<input id="txtBranchId", type="hidden" value="#Model.AgentImpersonatedBranchID" />
<input id="txtUserName", type="hidden" value="#Model.AgentImpersonatedUserName" />
<input id="hidCurrentController" type="hidden" value='#ViewContext.Controller.ValueProvider.GetValue("controller").RawValue' />
</li>
<li>
<label for="AgentImpersonatedUserName">User ref: *</label>
<select id="ddlUser" disabled="disabled" ></select>
<input type="hidden" id="txtUserID" />
#Html.HiddenFor(model => model.AgentImpersonatedUserName, new {ID="AgentImpersonatedUserName" })
#Html.HiddenFor(model => model.AgentImpersonatedBranchID, new {ID="AgentImpersonatedBranchID"})
#Html.ValidationMessageFor(model => model.AgentImpersonatedUserName, "*")
</li>
</ul>
Now in Chrome, all works fine. But when I fill in the form in IE but not the data in the Editor, it passes validation incorrectly!
If I examine the markup, using IEs poor excuse for Firebug, I can see the validation attributes,
<input name="ImpersonatedAgentModel.AgentImpersonatedBranchCode" id="txtBranchCode" type="text" data-val="true" data-val-required="Please provide a ABTA/AgencyCode" jQuery172048066185567747205="94"/>
so why is IE ignoring them!! Stupid IE
Found the answer here:
http://www.tigraine.at/2011/08/26/jquery-validate-and-microsofts-unobtrusive-validation-dont-play-well-together/
We were referencing Jquery validation too and this seems to be causing this error

Resources