I am new to MVC and trying to pass the last created Id (once the save button has been clicked in the form).
Can anyone please tell me if it is possible to pass this value to the toastr display, and how this can be done, so once the save button is pressed it returns that Id number?
Additionally to my comment, here's a more complex answer.
Roughly it contains the following items:
Views: CreateItem, NewItemHandler
Controllers: ItemHandler
Javascript: site.js and jQuery
The CreateItem view is the dialog where the user enters their item values. In my case a simple form with two input fields and the mandatory submit button.
#{
ViewBag.Title = "CreateItem";
}
<h2>CreateItem</h2>
<form id="newItemForm">
Item name: <input id="itemname" type="text" name="fname"><br>
Item weight: <input id="itemweight" type="text" name="lname"><br>
<input type="submit" value="Submit">
</form>
The JavaScript should stop the redirection when clicking on submit, this is done by returning false within $("newItemForm").submit(...). Furthermore we no need to tell the server that it needs to create our item, so we have to create our own submit request, which I did with jQuery.post():
$('#newItemForm').submit(function () {
sendPostAndShowResult();
return false;
});
function sendPostAndShowResult() {
var name = $("#itemname").text();
var weight = $("#itemweight").text();
$.post("/Item/NewItemHandler",
{ "name": name, "weight": weight }
).done(function (data) {
alert("The ID of your new item is: " + $.trim(data)); //replace with toast
})
.fail(function () {
alert("Error while processing the request!");
});
}
Just a hint: I didn't use toast here, since I never used it, but I guess it shouldn't be too difficult to adapt.
The final piece of the puzzle is the NewItemHandler, which creates the item, figures out the ID and returns the value:
The View is quite easy. Since we don't need a Layout, it has been set to "".
#{
Layout = "";
}
#Html.Raw(Session["ItemID"])
As you see, we just need to get the "ItemID" into our Session object, this is done by the Controller.
[HttpPost]
public ActionResult NewItemHandler(string name, string weight)
{
int id = GenerateNewItem(name, weight);
Session["ItemID"] = id;
return View();
}
EDIT: I tried to adapt this approach to your solution:
You need to remove the return RedirectToAction() with return View(); in your Controller. This then returns (Save.cshtml) a response, with the ID in an ohterwise empty file (Layout = "").
Your Save.cshtml is empty I guess, so replace it with
#{
Layout = "";
}
#Html.Raw(Session["ItemID"])
In your controller the Save Method should look remotely like this.
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Save(BidstonHwrc bidstonhwrc)
{
_context.BidstonHwrc.Add(bidstonhwrc);
try
{
_context.SaveChanges(); //either all changes are made or none at all
}
catch (Exception e)
{
Console.WriteLine(e);
}
int id = bidstonhwrc.Id;
Session["ItemID"] = id;
return View();
}
In your MCN Form you need to give your <form> tag an ID, via Razor:
#using (Html.BeginForm("Save", "BidstonHwrc",FormMethod.Post, new { id = "SaveBidstonHwrc" }))
The javascript code should look like this, simply adapt the IDs:
$('#SaveBidstonHwrc').submit(function () {
sendPostAndShowResult();
return false;
});
function sendPostAndShowResult() {
//foreach Model/ViewModel Property one line e.g.
var Id = $("Id").text();
var McnNumber = $("McnNumber").text();
$.post("/BidstonHwrc/Save",
{ "Id": Id, "McnNumber": McnNumber }
).done(function (data) {
alert("The ID of your new item is: " + $.trim(data)); //replace with toast
$(location).attr('href', '/Home/Index') //Redirect to Home
})
.fail(function () {
alert("Error while processing the request!");
});
}
I uploaded a project that should represent your solution a bit.
You can download it here (28MB): Project download
Related
I am getting value in a dropdown list and I wanted to get the selected value in controller when user select any value from the dropdown list. My view is -
#using (Html.BeginForm("ApReport", "Sales", FormMethod.Post))
{
#Html.DropDownList("Ddl", null, "All", new { #class = "control-label"})
#Html.Hidden("rddl")
}
controller -
[HttpPost]
public ActionResult ApReport(ApReport Ddl)
{
string Ddlvalue = string.Empty;
if (Request.Form["rddl"] != null)
{
Ddlvalue = Request.Form["rddl"].ToString();
}
}
but I am not getting any value. Also, I donot want to use any submit button.
Thanks in advance
The use of Ajax allows you as the developer to update the main view without reloading the entire page, as well as send data to the server in the background.
This is how I would have accomplished this task.
Firstly, I would have created an action in my controller which returns a JsonResult. This will return a JSON object to your calling jquery code, that you can use to get values back into your views. Here is an example of the action method.
[HttpGet]
public JsonResult YourActionName(string selectedValue) //Assuming key in your dropdown is string
{
var result = DoYourCalculation(selectedValue);
return Json(new { myResult = result }, JsonRequestBehavior.AllowGet);
}
Now, you need to add your jquery code. I would recommend you place this in a seperate javascript file referenced by your view.
Here is the JQuery code, with the ajax call to the Action in your controller. The Ajax call to the server is initiated by the 'change' event of your DropDown, handled in JQuery, as can be seen below.
$(function () {
$(document)
.on('change', '#Ddl', function(){
var valueofDropDown = $(this).val();
var url = '/YourControllerName/YourActionName';
var dataToSend = { selectedValue: valueofDropDown }
$.ajax({
url: url,
data: dataToSend,
type: 'GET',
success: function (dataReceived) {
//update control on View
var receivedValue = dataReceived.myResult ;
$('YourControlIDToUpdate').val(receivedValue);
}
})
});
};
Is there any way to get the name of View that called method in controller and save it for example in some custom variable inside that controller's method?
For example:
I have one View that uses Ajax to get to InfinateScroll method in controller:
<div class="container-post">
<div id="postListDiv">
#{Html.RenderAction("PostList", "Posts", new { Model = Model });}
</div>
<div id="loadingDiv" style="text-align: center; display: none; margin-bottom: 20px;">
<img alt="Loading" src="#Url.Content("~/images/ajax-loader.gif")" />
</div>
</div>
<script src="#Url.Content("~/Scripts/jquery-1.10.2.min.js")"></script>
<script type="text/javascript">
var BlockNumber = 2;
var NoMoreData = false;
var inProgress = false;
$(window).scroll(function () {
if ($(window).scrollTop() == $(document).height() - $(window).height() && !NoMoreData && !inProgress) {
inProgress = true;
$("#loadingDiv").show();
$.post("#Url.Action("InfinateScroll", "Posts")", { "BlockNumber": BlockNumber },
function (data) {
BlockNumber = BlockNumber + 1;
NoMoreData = data.NoMoreData;
$("#postListDiv").append(data.HTMLString);
$("#loadingDiv").hide();
inProgress = false;
});
}
});
</script>
I use this View on two pages. In one case I'm using it to show only posts from specific user (user who is logged in), and on the other view I'm showing posts from all users in database(similar to Facebook wall where you can see only your post, and NewsFeed where you can not only your's but also posts from your frineds).
For some reason I would like to know which page was active when call for InfinateScroll method was made.
This is the method where I would like to make some differences between those two pages so I can do some check out's later.
[HttpPost]
public ActionResult InfinateScroll(int BlockNumber)
{
int BlockSize = 5;
var posts = PostManager.GetPosts(BlockNumber, BlockSize);
JsonModel jsonModel = new JsonModel();
jsonModel.NoMoreData = posts.Count < BlockSize;
jsonModel.HTMLString = RenderPartialViewToString("PostList", posts);
return Json(jsonModel);
}
This method gets posts using helper method GetPosts and it's used for showing more posts on scroll.
You can get the name of the current View from inside the view using the following:
#Path.GetFileNameWithoutExtension(Server.MapPath(VirtualPath))
Source: How to get the current view name in asp.net MVC 3?
so you could add this as a routevalue into your #Url.Action like so:
#Url.Action(
"InfinateScroll",
"Posts",
new{callingView=Path.GetFileNameWithoutExtension(Server.MapPath(VirtualPath))})
Then you could add a parameter to your controller method
public ActionResult InfinateScroll(int BlockNumber, string callingView)
You can create a hidden variable in the html like this -
<input type="hidden" id="pageName" value="myPage1" />
Add an extra parameter to your Action -
public ActionResult InfiniteScroll(int BlockNumber, int pageName)
And then, in your jquery code, when you post, send in pageName as well.
$.post("#Url.Action("InfinateScroll", "Posts")", { "BlockNumber": BlockNumber, "pageName": $('#pageName').val() },
Hope this helps.
In one case I'm using it to show only posts from specific user... and
on the other view I'm showing posts from all users in database...
Putting your desired logic on the view is unsafe, especially if showing data is user-based or user-specific. However, if you insists on having the logic on the view then you should pass along another variable to the controller like so:
$.post("#Url.Action("InfinateScroll", "Posts")",
{ "BlockNumber": BlockNumber, "UserId": userId },
// rest of your code goes here...
});
You then should have another parameter in your controller:
[HttpPost]
public ActionResult InfinateScroll(int BlockNumber, int userId)
{
//filter your data based on the "userId" parameter
}
But like I mentioned this is unsafe because someone can easily pass in a valid "userId" and get to the data when you don't want them to. So the safest (or safer) way is to have the "filtering logic" in your controller like so:
[HttpPost]
public ActionResult InfinateScroll(int BlockNumber)
{
// a context based logic
var userId = GetLoggedInUserId();
// that method could return null or zero
// and depending on how you approach it
//filter your data based on the "userId"
}
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 have a view the contains an #Html.DropDownListFor. When the form/view loads, if one of the model properties has values (IEnumerable), then it will create a bunch of divs with the corresponding data. If that property does not have any values (aka Count() == 0), then it is supposed to show a single button on the form (which will create the data for that property).
So, when the user selects one of the options from the Dropdown, I fire an ajax call to the exact same action method that populated the current form/view, but this time, it sends a value in the id field.
I have a breakpoint in my action method and I verified that it is getting hit, and it has the correct parameter value and creates the correct data for the model that gets passed to the view, BUT...when the model is sent to the view to re-populate, NONE of the items/controls on the form change. I even put breakpoints in the cshtml file and it's going through there with the correct data also.
So, here's my controller:
public ActionResult Index(int? id)
{
var seasonId = id;
if (seasonId == null)
{
var player = _playerRepository.Query().FirstOrDefault(p => p.PlayerId == _userIdentity.PlayerId);
if (player.DefaultSeasonId != null)
seasonId = (int)player.DefaultSeasonId;
else
{
return View(new ScheduleModel
{
Player = player,
AvailableSeasons = _seasonRepository.Query().Select(s => s)
});
}
}
return View(CreateScheduleModelForSeason((int)seasonId));
}
Here's the beginning of my view:
#model LeagueManager.Models.ScheduleModel
#{
ViewBag.Title = "Schedule(s)";
}
<div class="row">
#Html.LabelFor(m => m.AvailableSeasons)
#Html.DropDownListFor(m => m.SelectedSeasonId, new SelectList(Model.AvailableSeasons, "SeasonId", "SeasonName"), new { id = "seasonSelect" })
</div>
<form method="post" action="Schedule/GenerateSchedule">
<h2>The Season's Schedules/Weeks and Matchups</h2>
<div>
<div>
#if (Model.SchedulesAndMatches == null || (Model.SchedulesAndMatches != null && !Model.SchedulesAndMatches.Any()))
{
<input type="submit" class="btn btn-primary" value="Generate Schedule" />
}
</div>
And here's the ajax call:
#* Season Selector *#
$('select#seasonSelect').change(function () {
var selectedSeasonId = $(this).val();
$.ajax({
url: '/Schedule/Index',
data: { id: selectedSeasonId }
});
});
Again, all of the actual code is working, it's just not re-rendering the view...
Example: when calling the ActionResult method with an id = 1, it loads the entire schedule. When switching to id = 2 via the dropdown (then getting called again via the ajax), it stays with the same schedule.
On the flip side: when calling the ActionResult method with an id = 2, it loads the single button. When switching to id = 1 via the dropdown, it re-populates the correct data in the model, but the view/form does not reflect the new information.
Please help!
When you call action using ajax you can’t return view, you have to return json data.
So your solution is remove ajax call and set window.location with your post url..
#* Season Selector *#
$('select#seasonSelect').change(function () {
var selectedSeasonId = $(this).val();
window.location = '/Schedule/Index/' + selectedSeasonId;
});
I have a form inside a partial view, it works but in case a server side validation error is raised it displays only the partial view. So I decided to use ajax for the submission (actually it makes sense as it is inserting a contact and there's a list in the main view).
Thing is, with the posted code if one of these errors comes it's properly displayed in the view (I'd need to make the partial visible again, but that's another thing), but if there's no error it'll display the list only in the partial view. I can do the other way round, displaying properly when there's no error but then not achieving the proper displaying of validation errors.
I would like to understand what's the best approach, or at least which are the possibilities: maybe change the code in the controller or do some kind of check in the success callback...
I edit what I had before, because when no error I should return the list, not the whole view as I posted before, but anyway I still have the doubt on how to tell one from another as both are succesfull calls to the post action
Thanks
The view is this one
#model ContactListViewModel
#{
ViewBag.Title = " My Contacts"
}
<div id="ContactList">
<h2>My Contacts</h2>
<hr />
<div id="addContainer">
#{ Html.RenderAction("AddContact"); }
</div>
<div id="editContainer" data-amp-url="#Url.Action("Edit", "Contacts")" class="initiallyHidden"></div>
#foreach (var group in Model.Contacts)
{
<div class="PlanContacts">
<div class="PlanName">#group.Key</div>
#foreach (var contact in group.Values)
{
<div class="Preview">
#Html.DisplayFor(m => contact, "Contact")
</div>
}
</div>
}
</div>
#section PageJavascript
{
<script src="~/Scripts/AMPContacts.js"></script>
}
The controller post action
[HttpPost]
public ActionResult AddContact(AddContactViewModel viewModel)
{
var partyId = (int) Session["PartyId"];
if (ModelState.IsValid)
{
_contactsManager.AddContact(viewModel, partyId);
// Here I should return the updated list
}
var newViewModel = _createBuilder.Rebuild(viewModel, partyId);
return PartialView("_AddContact", newViewModel);
}
And the ajax submission code inside the longer AMPContact.js
$('#addForm').submit(function (e) {
e.preventDefault();
var addContainer = $(document.getElementById('addContainer'));
$.ajax({
url: this.action,
type: this.method,
data: $(this).serialize(),
success: function(result) {
addContainer.html(result);
}
});
});
I understand this answer it's far from being good but maybe it's useful for somebody in terms of narrowing a possible search. As it would ended up being huge I rather post this update as an answer to my own question, not sure if it complies with good manners.
The explained one wasn't the only problem I faced, hard times as well with the reset of the form and specially the successive error submissions (once I got an error trying to submit it with errors again), so I ended up making a mess out of different solutions for different problems. Hopefully I will be able to clean it up
Thanks
In the view I use now
<div id="myContacts">
<h2>My Contacts</h2>
<hr />
<div id="addContainer">
<div class="toggler">
Add Contact
</div>
<div id="addToggling" class="initiallyHidden">
#{ Html.RenderAction("AddContact"); }
</div>
</div>
<div id="editContainer" data-amp-url="#Url.Action("Edit", "Contacts")" class="initiallyHidden"></div>
<div id="list">
#{ Html.RenderPartial("_ContactList", Model); }
</div>
In the .js
$('#addContainer').on('submit', '#addForm', ajaxCall);
function ajaxCall(e) {
e.preventDefault();
var addToggling = $(document.getElementById('addToggling'));
var contactList = $(document.getElementById('contactList'));
$.ajax({
url: this.action,
type: this.method,
data: $(this).serialize(),
success: function (result) {
if (result.passedValidation == true) {
// Json is returned with flag, we get the list from the server and update the list element
$.get(result.action, function (partial) {
contactList.html(partial);
});
// Add some effects and clear the form
$(document).scrollTop(0);
setTimeout(function () {
addToggling.slideUp(300, resetAddForm);
}, 500);
setTimeout(function () {
contactList.effect("highlight", {}, 3000);
}, 1000);
}
else {
// The form partial view is returned and displayed in the same element when there are validation errors
$(document).scrollTop(0);
addToggling.html(result);
$.validator.unobtrusive.parse('#addForm');
}
}
});
}
function resetAddForm() {
var addForm = $(document.getElementById('addForm'));
// Hhide the error messages
addForm.find("span.field-validation-error").hide();
addForm.find("div.validation-summary-errors").hide();
// Removes the class associated to errors
addForm[0].reset();
// Clear the inputs
addForm.find('input:text, input:password, input:file, select, textarea').val('');
addForm.find('input:radio, input:checkbox').removeAttr('checked').removeAttr('selected');
}
Controller with the existing action method slightly changed and a new one
public ActionResult ContactList()
{
var partyId = (int)Session["PartyId"];
var viewModel = _displayBuilder.Build(partyId);
return PartialView("_ContactList", viewModel);
}
[HttpGet]
public ActionResult AddContact()
{
var partyId = (int) Session["PartyId"];
var viewModel = _createBuilder.Build(partyId);
return PartialView("_AddContact", viewModel);
}
[HttpPost]
public ActionResult AddContact(AddContactViewModel viewModel)
{
var partyId = (int) Session["PartyId"];
if (ModelState.IsValid)
{
_contactsManager.AddContact(viewModel, partyId);
if (Request.IsAjaxRequest())
return Json(new { passedValidation = true, action = Url.Action("ContactList")});
return RedirectToAction("Index");
}
var newViewModel = _createBuilder.Rebuild(viewModel, partyId);
return PartialView("_AddContact", newViewModel);
}