How do I render a view from a razor page handler? - c#

I have a collection of items in which I need the user to be able to add and remove them without actually making a post. The page gets rendered with 2 items by default. The user can then add and remove as they would like. Then, when they finish, there's a submit button that will trigger the post.
I can't seem to get my EditorTemplate to render. I keep getting a 500 error.
Shared/EditorTemplates/MyObjectModel
#model Models.Common.MyObjectModel
<div>
<h5>User ##Model.UserId</h5>
</div>
<div>
<select asp-for="#Model.UserType"
asp-items="#(new SelectList((Dictionary<string, Models.Enums.UserTypes>)ViewData["MyUserTypes"],"Value","Key"))"></select>
</div>
I'm using an Ajax GET to call this handler:
public IActionResult OnGetEmptyStopTemplate()
{
var viewDataBase = new ViewDataDictionary(new Microsoft.AspNetCore.Mvc.ModelBinding.EmptyModelMetadataProvider(),
new Microsoft.AspNetCore.Mvc.ModelBinding.ModelStateDictionary())
{
{ "MyUserTypes", UserTypes}
};
ViewDataDictionary<MyObjectModel> viewdata = new ViewDataDictionary<MyObjectModel>(viewDataBase , new MyObjectModel());
return new PartialViewResult() { ViewName = "~/Shared/EditorTemplates/MyObjectModel", ViewData = viewdata };
}

Related

How can i send back a model containing two lists from the view to my controller

I have a cshtml view, for which I am sending a viewmodel. That viewmodel consist of two list of albums (music albums). I then ask the user to check up to 3 of these albums (a checkbox next to the title) to vote for his favorite music. I use javascript to ensure he doesn't check anymore than 3 (The security is a detail right now, I'm more concerned about getting it to work, but I'm open to suggestion if people have a better solution).
Since all albums are displayed in a table, I would love to send back to the controller through the submit button, the same model after updating it.
Basically, one of the list contain the current vote the user has made before loading the page (can be empty), and the second one should be empty until sent back to the controller containing the list of votes that are currently selected. I then use these two lists to compare them and update the database, removing the votes he removed, and adding the vote he added.
But I am unable to create a proper form to return these informations as I am not used to forms.
I tried to put the whole list in a form, but it didn't work. My reserach when I look for "sending model back to controller" usually do just that and get it to work.
View model
public class CategoryVotesUserViewModels
{
public CategoryVoteViewModels categoryVoteViewModels;
public List<int> listVotesEntry = new List<int>();
public List<int> listVotesOutput = new List<int>();
}
Relevant CSHTML and javascript
#section Header{
<script>
var MAX_VOTES = 3;
function checkNumberVotes($this) {
console.log($("input[name='listVoteOutput']:checked"));
if ($("input[name='listVoteOutput']:checked").length > MAX_VOTES) {
$this.checked = false;
}
}
</script>
}
#using (Html.BeginForm("VoteInCategory", "Votes", new { SearchModel = Model }, FormMethod.Post))
{
<ul>
#foreach (var av in Model.categoryVoteViewModels.listVotes)
{
<li>
#av.album.Title | #av.votes |
<input type="checkbox"
name="listVoteOutput"
value=#av.album.ID
onclick="checkNumberVotes(this)"
#if (Model.listVotesEntry.Contains(av.album.ID))
{ <text> checked </text> } />
</li>
}
</ul>
<div class="form-group text-center">
<input type="submit" class="btn btn-primary" value="Submit" />
</div>
}
Controller
[HttpPost]
public ActionResult VoteInCategory(CategoryVotesUserViewModels categoryVotesUserViewModels)
{
if (ModelState.IsValid)
{
List<int> toAdd = categoryVotesUserViewModels.listVotesOutput.Except(categoryVotesUserViewModels.listVotesEntry).ToList();
List<int> toRemove = categoryVotesUserViewModels.listVotesEntry.Except(categoryVotesUserViewModels.listVotesOutput).ToList();
VoteService.updateVoteUserCategory(User.Identity.GetUserId(), toRemove, toAdd, categoryVotesUserViewModels.categoryVoteViewModels.categoryID);
//TODO Redirect to success
return RedirectToAction("Index", "Home");
}
return View(categoryVotesUserViewModels);
}
If the user already had voted, all album whose ID is in "ListVotesEntry" should begin checked. If the user hasn't voted, or voted for nothing previously, "ListVotesEntry" should be empty.
When the User press the submit button, if an album is checked, the album's id should be added to the "ListVotesOutput" list. Also, both "ListVotesEntry" and "ListVotesOutput" should be sent back to the controller. The list with the names of the albums and their titles/ID is no longer necessary for the rest of the treatment
Found the solution. The problem was that my model needed to use "{get; set;}" on its attributes, otherwise the binding doesn't work, which mean that it send back an empty model.

Get RadioButton Value KnockoutJs

I have dynamically created radio buttons in my view and i am trying to pass the checked value to my controller. When my controller is hit the Agentcode string is Empty and i cant figure out how to grab the value so i can send it to my controller.
EDIT my radio buttons are generated in a foreach which i think may be causing the difficulty in reaching the value.
This is what the html rendered by the MVC control looks like
<div>
<input name="XXXXX" type="radio" value="{ data_bind = checkedValue: $data,checked: $root.AgentCode }">
</div>
foreach (var code in Model.ActiveAgentCodes)
{
<div>
#Html.RadioButton(code.AgentCode, new { data_bind="checkedValue: $data,checked: $root.AgentCode"})
#Html.RadioButton(code.AgentCode, new {data_bind="checkedValue: $data,checked: $root.AgentCode"}) }
my knockout ViewModel looks like this.
function ViewModel(){
var self = this;
self.AgentCode = ko.observable();
};
var viewModel = new ViewModel();
ko.applyBindings(viewModel);
and the post method in my controller looks like this
[HttpPost]
public ActionResult GetAgentCodeForHomeController(string AgentCode)
{
return RedirectToAction("Home", "Home");
}
In my view i am posting using like so
#using (Html.BeginForm("GetAgentCodeForHomeController", "ChangeAccount"))
{
#Html.RadioButton(code.AgentCode, new {data_bind="checkedValue: $data,checked: $root.AgentCode"})
#Html.HiddenFor(model => model.AgentCode, new { data_bind = "text:AgentCode" })
}
<button type="submit">OK</button>
I needed to send data back to my controller and the best way that i found was to add properties to my viewmodel.
public string AgentCode {get; set;}
Then give the radio buttons an Id so that when the controller is hit MVC maps the properties correctly. I decided to not go with knockout and instead post the form back to the controller. It ended up looking like this on my buttons.
#Html.RadioButton("AgentCode", code.AgentCode, new {id = code.AgentCode})

Best way to handle add/view/delete on one page

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...)

MVC dynamic pages from list

I have a page which represents data from ICollection<> model, it generates #Html.BeginForm() for each item in ICollection<> and shows data with #Html.Labels, and I want to create a link from each form to item details, so it will be like, when I press form with item with id=4, it sends Model.ElementAt(4) as a model to new page, and display it. How can I do that?
EDIT: I guess I need to add something like #Html.ActionLink("DetailsPage","Shops",shop)
#using WebShops
#model ICollection<WebShops.Shop>
#foreach (Shop shop in Model)
{
using (Html.BeginForm())
{
#Html.Label(shop.name)
#Html.Display(shop.name)
<br />
#Html.Label(shop.image)
#Html.Display(shop.image)
<hr />
}
}
To display a specific item there is no need for Html.BeginForm because it makes a POST request and you need to make a GET request.
You need to create a new Action that will make use of GET request.
public ActionResult Shop(string id)
{
var shop = //get shop by id from database
return View(shop)
}
You call the new action like below.
#using WebShops
#model ICollection<WebShops.Shop>
#foreach (Shop shop in Model)
{
#Html.Label(shop.name)
#Html.Display(shop.name)
<br />
#Html.Label(shop.image)
#Html.Display(shop.image)
<hr />
#Html.ActionLink("Display", "Shop","ControllerName", new {id = shop.id})
}
You can do that using the object routeValues from this overload of Html.ActionLink:
#Html.ActionLink("DetailsPage","Shops", new { id = shop.ID })
This doesn't "send the model to the new page", it makes a link to Shops/DetailsPage/4, causing GET request when clicked.
So in the DetailsPage action method you'll have to look up the shop on ID again in order to display it.

Reloading View does not update all of the controls on the form

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;
});

Categories

Resources