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"
}
Related
So I have a list, where useer can add any number of items. In other words, I have a <ul> and variable length of <li> elements.
Variable length <li> elements means, user adds <li> elements in runtime, if she/he is done, she/he submits the form. So I don't know how many <li> elements are there.
Something like this :
<form asp-controller="MyController" asp-action="AddList" method="post">
<ul id="myUL">
<li>item1</li>
<li>item2</li>
/*...*/
<li>itemN</li>
</ul>
<button type="submit" class="btn btn-default">New List</button>
</form>
I add <li> elements dynamically with JavaScript if user clicks a button, like this:
function newElement() {
var li = document.createElement("li");
var inputValue = document.getElementById("myInput").value;
var t = document.createTextNode(inputValue);
li.appendChild(t);
document.getElementById("myUL").appendChild(li);
}
My controller's method (AddList) looks like this:
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<IActionResult> NewShoppingList(object list)
{
// I don't know how to accept that list
}
But this way my parameter list is null. How can I get that <ul> with some <li> elements?
ul and li are not submittable form elements.You can try the following.
I'm not sure what your view model looks like, but it seems like a list of string values? So then it will look like this in your view model:
public class YourViewModel
{
public List<string> Items { get; set; }
}
In your view try the following:
<ul id="myUL">
<li>item1</li>
<li>item2</li>
/*...*/
<li>itemN</li>
</ul>
function addHidden(theLi, key, value) {
// Create a hidden input element, and append it to the li:
var input = document.createElement('input');
input.type = 'hidden';
input.name = key;'name-as-seen-at-the-server';
input.value = value;
theLi.appendChild(input);
}
function newElement() {
var li = document.createElement("li");
var inputValue = document.getElementById("myInput").value;
var t = document.createTextNode(inputValue);
li.appendChild(t);
addHidden(li, 'your-model-propertyName' + li-Count+1, inputValue );
document.getElementById("myUL").appendChild(li);
}
When you post then these items should still be in the list.
[HttpPost]
[AllowAnonymous]
//[ValidateAntiForgeryToken]
public async Task<IActionResult> NewShoppingList(List<string> list)
{
// I don't know how to accept that list
}
If you dont like add hidden field for each li , you can get all li items and send thems with Ajax.
like this:
function sendData() {
var items = [];
$("#myUL li").map(function () {
items.push(this.innerText);
});
$.ajax({
type: "POST",
data: {
list: items
},
url: "/Home/NewShoppingList",
success: function (res) {
}
}
I hope this helps.
You should avoid posting the html, after all you'll probably need to parse it, save to db etc. As Jeremy Thomson suggested use some client side library - jQuery, Angular or what ever you want and do the posts of items using Ajax. You can post them on each user input, or keep them in mvc model, javascript object etc. and post them at once.
example just to get the idea:
MVC - Binding dynamically added controls to a List<T> in a Model
I would like to create a view that would contain a different view. I've never used json before. How i can do this and How can I format the json data in the view?
My first function "Details" is to retrieve a object from the database and return view "Details.cshtml". In this view I want generates a partial view ("Stats.cshtml"). And now I want to generate a partial view with the data downloaded in the json format inside the Stats function.
Controller
public IActionResult Details(int? id = 1)
{
var person = _context.Persons.Find(id);
return View(champion);
}
public IActionResult Stats()
{
var json = new WebClient().DownloadString("url");
return Json(s);
}
View - Details.cshtml
#model Person
<div class=row">
<div class="col-sm-5"> #Model.Name </div>
<div class="col-sm-5"> #Html.Partial("Stats") </div>
</div>
View - Stats.cshtml
<h2>Stats</h2>
<div> here I want to put in a json field </div>
When I run "Stats" function from the address localhost/Home/Stats I get the result in json, but when I run "Details" function I get view "Details" and "Stats" without the json value.
to render a partial, you have many options, by your code,
the simplest one is: move your Stats code to Details action
public ActionResult Details()
{
...//prepare your person viewModel
var result = new WebClient().DownloadString("url");
var stats = JsonConvert.DeserializeObject<YourViewModel>(result);
//you have 2 options to return data
yourPersonModel.Stats=stats ; //<== you have to change PersonViewModel
//or ViewBag.Stats=stats;
return View(yourPersonModel);
}
then in Details.cshtml:
#Html.Partial("Stats", ViewBag.Stats or Model.Stats)//by your choice before.
Since Html.Action is removed, but ViewComponent comes in Core, you cannot directly call it right now, but this link will tell you how to make it back: #Html.Action in Asp.Net Core
public ActionResult Stats()
{
var result = new WebClient().DownloadString("url");
var yourViewModel = JsonConvert.DeserializeObject<YourViewModel>(result);
return PartialView(yourViewModel);
}
add the following code in your View - Stats.cshtml:
#model YourViewModel
then in Details.cshtml:
#Html.Action("Stats")
Be aware that Html.Action cannot call async actions, be careful to use it.
the next solution is to use new feature ViewComponent, here is details:
https://learn.microsoft.com/en-us/aspnet/core/mvc/views/view-components?view=aspnetcore-2.1
the last one will not be what you expected: use AJAX to load this partial page on page Details is loaded to client
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
What I want to do
I am very new to MVC.
I'm trying to create a page that allows users to perform the following actions on the same page:
View the list (table)
Add a new item (Filling the form and clicking the Add button should update the table)
Delete an item from the list (Clicking the Delete button in a row should update the table)
A simple example looks like this but I actually have two lists on one page (Fees and Costs):
Question
What would be the best way to achieve this?
Should I go with Dylan Beattie's method posted here which would look something like this?
public ActionResult MyAction(string submitButton, MyViewModel form)
{
switch (submitButton)
{
case "AddFee":
return (AddFee(form));
case "AddCost":
return (AddCost(form));
case "RemoveFee":
return (RemoveFee(form));
case "RemoveCost":
return (RemoveCost(form));
}
}
public ActionResult AddFee(MyViewModel form)
{
Fee newFee = ....; // Get entered data from `form`
_repository.InsertFee(newFee);
return View("Create"); //Back to the original page
}
Or is there any other recommended methods to handle this such as using JavaScript?
You could create the table as a partial view and re render this via ajax.
Wrap the partial view in a div and Wrap the form in #using (Ajax.BeginForm(.... and target the wrapper div. Your controller action that is targeted by the ajax request will need to return a partial view.
Here is a simple example
public class HomeController : Controller
{
public ActionResult Index()
{
MYvm vm = new MYvm() { id = 1, name = "This is my View Model" };
return View(vm);
}
public ActionResult DA(MYvm vm)
{
vm.name = "CHANGED";
return PartialView("Part", vm);
}
View:
#model MvcApplication1.Controllers.HomeController.MYvm
#{
ViewBag.Title = "Home Page";
}
#using (Ajax.BeginForm("DA", "Home", new AjaxOptions() { UpdateTargetId = "cont", HttpMethod = "Get" }))
{
<div>
Id: #Html.EditorFor(model => model.id)
</div>
<div>
Name: #Html.EditorFor(model => model.name)
</div>
<input type="submit" value="SubmitForm" />
}
<div id="cont">
#{Html.RenderPartial("part", Model);}
</div>
Partial View
#model MvcApplication1.Controllers.HomeController.MYvm
#{
ViewBag.Title = "part";
}
<h2>part</h2>
#Model.name
Should I go with [previous SO answer]
No. That answer was for a different scenario where the question had a form with two submit buttons that wanted to do two different actions (and wasn't even the accepted answer to that question).
Your sample screenshot indicates that some javascript/jquery and ajax would solve the issue cleanly.
As you're new to MVC, try to keep it relatively simple. Break up the page into separate parts:
the containing page
the edit form
the list with remove
the edit/list work independently and should be written in a way that they could be put on any other page - the page is just there to contain them and doesn't do much else (obviously your real page will contain more, but add those parts as separate components as well).
1 Create actions for your list and edit forms that return partialviews - just the parts that are needed for that view (self-contained)
controller:
[HttpGet]
public ActionResult AddCost()
{
var model = new Cost();
return PartialView(model);
}
[HttpPost]
public void AddCost(Cost model)
{
if (ModelState.IsValid) {
db.SaveCost(model);...
}
}
form Views/Home/AddCost.cshtml:
#using (Ajax.BeginForm(...
{
<div class='editor-label'>#Html.LabelFor(model=>model.Description)</div>
...etc...
}
I'll leave you to set the Ajax.BeginForm properties. But make sure the on-success calls reloadCostList() (see below)
controller
public ActionResult CostList()
{
var model = db.loadCosts(); ...
return PartialView(model);
}
list, Views/Home/CostList.cshtml
#model IEnumerable<ViewModels.Cost>
<table>
<thead>
<tr>
<th>Cost Description</th>
...
<tbody>
#foreach (var cost in Model.Costs)
{
<tr data-id='#cost.Id'>
<td>#Html.DisplayFor(x=>cost.Description)</td>
...
<td><a href='#' class='remove-button'>Remove</a></td>
}
...
2 Create an action + view for the main page with placeholder for the form and calls the list partial-action, eg:
<div id="body">
<div id="formWrapper">
#Html.Action("AddCost")
</div>
<div id="listWrapper">
#Html.Action("ListView")
</div>
</div>
if you already load the data for the page, you can pass it directly to the partial, but there's no need:
#Html.Partial("ListView", Model.Costs)
this allows you to refresh the list via an ajax call, something like:
function reloadCostList() {
$(".listWrapper").load("Home/CostList");
}
(ideally, $.ajax and add some fancy UI to indicate loading)
3 Add a remove action to your controller
[HttpPost]
public void RemoveCost(int id)
{
}
4 Wire up the Remove link
$(function() {
$(".remove-button").click(function() {
var id = $(this).closest("tr").attr("id");
$.post("/Home/RemoveCost/" + id, null, function() {
$(".listWrapper").load("Home/CostList");
// or reloadCostList(); from above
// or:
//$(".listWrapper tr[id=" + id + "]").hide();
});
});
}
rather than re-load the entire list, you could just remove the row (add some fancy UI like fade-out...)
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;
});