Using forms in partial views in asp.net core razor pages - c#

I have the following structure:
SelectLoc.cshtml:
#model SelectLocModel
<div class="dropdown">
<form method="get">
<select asp-for="Location" asp-items="Model.Locations"
class="btn btn-secondary" formaction="Partials/SelectLoc" onchange="this.form.submit()">
</select>
</form>
</div>
SelectLoc.cshtml.cs:
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.AspNetCore.Mvc.Rendering;
using System.Collections.Generic;
using System.Net.Http;
using System.Net.Http.Json;
namespace AdminPortal.Web.Pages.Partials
{
public class SelectLocModel : PageModel
{
private readonly HttpClient httpClient;
private readonly string key = "FAKE TOKEN";
public string Location { get; set; }
public List<SelectListItem> Locations { get; } = new List<SelectListItem>
{
new SelectListItem { Value = null, Text = "Select Location" },
new SelectListItem { Value = "Kothrud", Text = "Kothrud" },
new SelectListItem { Value = "Dhanakawdi", Text = "Dhanakawdi" },
new SelectListItem { Value = "Karvenagar", Text = "Karvenagar" },
new SelectListItem { Value = "Wakad", Text = "Wakad" },
};
public SelectLocModel(HttpClient httpClient)
{
this.httpClient = httpClient;
}
public void OnSubmit()
{
}
public void OnGet()
{
}
public void OnGetSubmit()
{
}
public async void OnGetLocation()
{
string geocodeRequest = $"https://maps.googleapis.com/maps/api/geocode/json?address={Location}&key={key}";
Location jsonResponse = await httpClient.GetFromJsonAsync<Location>(geocodeRequest);
}
}
}
I know, that there isn't any useful code in any of the methods, but I want the form to use the OnGet handlers in the code-behind file. It somehow keeps calling the ctor. What am I doing wrong?

If you want to go to https://localhost:xxx/Partials/SelectLoc?Location=xxx when submit form,you can add action="/Partials/SelectLoc" in form.
SelectLoc.cshtml:
#model SelectLocModel
<div class="dropdown">
<form method="get" action="/Partials/SelectLoc">
<select asp-for="Location" asp-items="Model.Locations"
class="btn btn-secondary" formaction="Partials/SelectLoc" onchange="this.form.submit()">
</select>
</form>
result:

Related

How to pass Models to Controller for selected checkbox using MVC?

I have an ASP.NET Core MVC app. The view contains a dropdown list where multiple items of the same category can be selected. When I press "Submit", I would like to have the full model of SomeType (including Id, Name, Value, ExtraInfo). In the current set up I only get the Name of SomeType:
HomeController.cs
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using System.Collections.Generic;
using WebApp.Models;
namespace WebApp.Controllers
{
public class HomeController : Controller
{
private readonly ILogger<HomeController> _logger;
public HomeController(ILogger<HomeController> logger)
{
_logger = logger;
}
public IActionResult Index()
{
var viewModel = new MainViewModel()
{
SomeInfo = "test",
SomeModel = new SomeModel
{
Name = "Model1",
SomeType1 = new List<SomeType1>
{
new SomeType1 { Id = "1", Name = "Spinach", Value = "TXT_FLD_SPINA", ExtraInfo = "something1" },
new SomeType1 { Id = "2", Name = "Broccoli", Value = "TXT_FLD_BRO", ExtraInfo = "something else5" },
new SomeType1 { Id = "3", Name = "Wheatgrass", Value = "TXT_FLD_WHE", ExtraInfo = "something else4" },
},
SomeOtherType2 = new List<SomeType1>
{
new SomeType1 { Id = "1", Name = "Apple", Value = "TXT_FLD_APPLE", ExtraInfo = "something" },
new SomeType1 { Id = "2", Name = "Banana", Value = "TXT_FLD_BANA", ExtraInfo = "something else" },
new SomeType1 { Id = "3", Name = "Tomatoes", Value = "TXT_FLD_TOM", ExtraInfo = "something else2" },
}
}
};
return View(viewModel);
}
[HttpPost]
public IActionResult Index(string search, List<SomeType1> SomeType1, string[] SomeOtherType2)
{
return View();
}
}
}
MainViewModel.cs
using System.Collections.Generic;
namespace WebApp.Models
{
public class MainViewModel
{
public SomeModel SomeModel { get; set; }
public string SomeInfo { get; set; }
}
public class SomeModel
{
public List<SomeType1> SomeType1 { get; set; }
public List<SomeType1> SomeOtherType2 { get; set; }
public string Name { get; set; }
}
public class SomeType1
{
public string Id { get; set; }
public string Name { get; set; }
public string Value { get; set; }
public string ExtraInfo { get; set; }
}
}
Index.cshtml
#model MainViewModel
#{
ViewData["Title"] = "Home Page";
}
#using (Html.BeginForm("Index", "Home"))
{
<div class="text-center">
<input type="text" name="search" />
<input type="submit" value="Submit" />
</div>
<br />
foreach (var item in Model.SomeModel.SomeType1)
{
<b>Some Type 1</b>
<div class="checkbox">
<label>
<input type="checkbox"
name="SomeType1"
value="#item.Value" /> #item.Name
</label>
</div>
}
foreach (var item in Model.SomeModel.SomeOtherType2)
{
<b>SomeOtherType2</b>
<div class="checkbox">
<label>
<input type="checkbox"
name="SomeOtherType2"
value="#item.Value" /> #item.Name
</label>
</div>
}
}
According to your description, I suggest you could try to modify the view since the name for the checkbox should be the "SomeType1[0].Value" not the "SomeType1". Since the auto model binding will checked the form name when binding, this is the reason why the SomeType1 is null.
More details, you could refer to below codes:
#model MainViewModel
#{
ViewData["Title"] = "Home Page";
}
#using (Html.BeginForm("Index", "DropDownTest"))
{
<div class="text-center">
<input type="text" name="search" />
<input type="submit" value="Submit" />
</div>
<br />
for (int i = 0; i < Model.SomeModel.SomeType1.Count; i++)
{
<b>Some Type 1</b>
<div class="checkbox">
<label>
<input type="checkbox"
name="SomeType1[#i].Value"
value="#Model.SomeModel.SomeType1[i].Value" /> #Model.SomeModel.SomeType1[i].Name
</label>
</div>
}
for (int i = 0; i < Model.SomeModel.SomeOtherType2.Count; i++)
{
<b>SomeOtherType2</b>
<div class="checkbox">
<label>
<input type="checkbox"
name="SomeOtherType2"
value="#Model.SomeModel.SomeOtherType2[i].Value" /> #Model.SomeModel.SomeOtherType2[i]..Name
</label>
</div>
}
}
Result:

ASP.NET core 3 ActionFilter and Viewbag

I'm building a webpage containing some simple text fields and a dropdownlist. The values of this dropdownlist come from a database table and are populated via the Viewbag.
<div class="form-group">
<label asp-for="CategoryId" class="control-label"></label>
<select asp-for="CategoryId" asp-items="ViewBag.Categories" class="form-control"></select>
<span asp-validation-for="CategoryId" class="text-danger"></span>
</div>
For validation of my forms I'm trying to use an actionfilter.
public class ModelValidationFilter : Attribute, IActionFilter
{
public void OnActionExecuted(ActionExecutedContext context)
{
//Not needed
}
public void OnActionExecuting(ActionExecutingContext context)
{
if (!context.ModelState.IsValid)
{
context.Result = new ViewResult()
{
ViewData = ((Controller)context.Controller).ViewData,
TempData = ((Controller)context.Controller).TempData,
StatusCode = 400
};
}
}
}
This works really well, except for the dropdownlist, it seems like the viewbag is cleared.
Is there any way to make this construct work?
As you have passed tempdata to new result in actionfilter,you can use TempData to replace ViewBag.Here is a demo(.net core 3.1):
Controller(TempData cannot accept List in .net core3.1,so i serialize it in action and deserialize it in view):
[HttpGet]
public IActionResult Index()
{
var Categories=JsonConvert.SerializeObject(new List<SelectListItem> { new SelectListItem { Value = "1", Text = "c1" }, new SelectListItem { Value = "2", Text = "c2" }, new SelectListItem { Value = "3", Text = "c3" } });
TempData["Categories"] = Categories;
return View();
}
[HttpPost]
[ModelValidationFilter]
public IActionResult Index(Category c)
{
return View();
}
Index.cshtml(TempData.Peek can keep the tempdate value):
<form method="post">
<div class="form-group">
<label asp-for="CategoryId" class="control-label"></label>
<select asp-for="CategoryId" asp-items='#(Newtonsoft.Json.JsonConvert.DeserializeObject<List<SelectListItem>>(TempData.Peek("Categories").ToString()))' class="form-control"></select>
<span asp-validation-for="CategoryId" class="text-danger"></span>
</div>
<input type="submit" value="submit" />
</form>
Category:
public class Category
{
public string CategoryId { get; set; }
[Required]
public string CategoryName { get; set; }
}
result:

Passing a List from View to Controller in Model

I am trying to pass a complex data structure from Controller to View and Back to Controller which contains Lists. I can see the list items in View. I want to edit those and send it back to the controller. I am able to edit some properties but for lists, I am getting null value in the controller.
Here is an example (simulation) of what I am trying to achieve:
Consider Model -
using System.Collections.Generic;
namespace WebApplication1.Models
{
public class StudentViewModel
{
public string StudentId { get; set; }
public string FeedBack { get; set; }
public List<ScoreCard> ScoreCards;
}
public class ScoreCard
{
public string Subject { get; set; }
public string Marks { get; set; }
}
}
Controller -
public class StudentController : Controller
{
public ActionResult Index()
{
var model = new StudentViewModel();
model.StudentId = "Some Student";
model.ScoreCards = new List<ScoreCard>
{
new ScoreCard()
{
Marks = "0",
Subject = "English"
},
new ScoreCard()
{
Marks = "0",
Subject = "Maths"
}
};
return View("View", model);
}
public ActionResult SubmitScore(StudentViewModel model)
{
/* Some Code */
}
}
View -
#model WebApplication1.Models.StudentViewModel
#{
ViewBag.Title = "Title";
}
#using (Html.BeginForm("SubmitScore", "Student", FormMethod.Post))
{
#Html.DisplayName(#Model.StudentId)<br />
<label>Comment:</label><br/>
#Html.EditorFor(m => m.FeedBack, new { htmlAttributes = new { #type = "text", id = #Model.FeedBack} })<br />
for (var i = 0; i < #Model.ScoreCards.Count; i++)
{
#Html.DisplayName(#Model.ScoreCards[i].Subject) <br/>
#Html.EditorFor(m => m.ScoreCards[i].Marks, new { htmlAttributes = new { #type = "number", #min = 0, id = #Model.ScoreCards[i].Marks} })<br />
}
<input class="btn btn-primary" type="submit" value="Submit" />
}
When I run the application -
When I click submit, I am able to see model.FeedBack but the list is set to null.
Something similar is achieved in this question & it's answer; I am not sure what exactly I am missing here.
you send no input for the Subject . You'll need a hidden input for that along with any other value you want returned to the controller when the form is posted. Also a simple text box should work for the marks.
#using (Html.BeginForm("SubmitScore", "Student", FormMethod.Post))
{
#Html.DisplayName(#Model.StudentId)<br />
#Html.HiddenFor(m => m.StudentId)
<label>Comment:</label><br/>
#Html.EditorFor(m => m.FeedBack, new { htmlAttributes = new { #type = "text", id = #Model.FeedBack} })<br />
for (var i = 0; i < #Model.ScoreCards.Count; i++) {
#Html.HiddenFor(m => m.ScoreCards[i].Subject)
#Html.DisplayName(#Model.ScoreCards[i].Subject) <br/>
#Html.TextBoxFor(m => m.ScoreCards[i].Marks, new { htmlAttributes = new { #type = "number", #min = 0} })<br />
}
<input class="btn btn-primary" type="submit" value="Submit" />
}
Finally you need to use properties in order for the model binding to work. You currently have ScoreCards as a field so update it to a property.
public class StudentViewModel {
public string StudentId { get; set; }
public string FeedBack { get; set; }
public List<ScoreCard> ScoreCards { get; set; }
}

All properties of my model are null in post method

In my program, I have a Rank view:
<html>
<head>
#{ViewBag.Title = "Rank";}
<link href="https://fonts.googleapis.com/css?family=Nunito|Raleway|Rubik" rel="stylesheet">
</head>
<body>
#model List<WebRanker.Models.MatchupModel>
<h2>Rank your list!</h2>
<h2 style="font-size: small">Choose your favorite of these two</h2>
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div id="rankradio">
#Html.RadioButtonFor(model => model[0].SelectedItem, Model[0].FirstItem, new { #checked = "checked" })
<label for="choice1">#Model[0].FirstItem.ItemName</label>
<br />
#Html.RadioButtonFor(model => model[0].SelectedItem, Model[0].SecondItem)
<label for="choice2">#Model[0].SecondItem.ItemName</label>
<div>
<button class="btn btn-success btn-large" type="submit">Next</button>
#Html.ActionLink("Cancel", "Index", "Collection", null, new { #class = "btn btn-danger btn-large" })
</div>
</div>
}
</body>
and Rank get/post methods in my controller:
[HttpGet]
public ActionResult Rank(int id)
{
var service = GetCollectionService();
if (!TempData.Keys.Contains("matchuplist"))
{
TempData["matchuplist"] = service.GetMatchups(id);
}
return View(TempData["matchuplist"]);
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Rank(MatchupModel matchup)
{
var service = GetCollectionService();
TempData["matchuplist"] = matchup.MatchupList.Skip(1); // Error here
service.IncreaseItemRankingPoints(matchup.SelectedItem.ItemID);
return View(matchup.SelectedItem.CollectionID);
}
And a model called MatchupModel:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using WebRanker.Data;
namespace WebRanker.Models
{
public class MatchupModel
{
public int ListID { get; set; }
public Item FirstItem { get; set; }
public Item SecondItem { get; set; }
public Item SelectedItem { get; set; }
public List<MatchupModel> MatchupList { get; set; }
}
}
When the user selects one of the radio buttons in the view and hits submit, I want it to set the SelectedItem property of the model and then send it to the post method in my controller. For some reason, ALL of the properties for my model are null when it reaches the controller, which causes it to break when it reaches TempData["matchuplist"] = matchup.MatchupList.Skip(1); with the error System.ArgumentNullException: 'Value cannot be null. Parameter name: source'.
I've looked everywhere and I have no idea how to fix this. I've tried using Request[string] with RadioButton() instead of RadioButtonFor() but that only stores the type of my model as a string instead of the actual model. I'd really appreciate some help, thanks!
Add a property named choice in your matchupmodel class and in the Radiobutton() put "[0].choice" instead of just "choice". Get that value in string variable and pass it to your service

DropDownList - How to add SelectListItem ASP.NET MVC

I have an DropDownList ,which is showing list of Status,but when i Select an item from DropDownlist and than i Checked HTML Markup i can see there isnt Selected attr and than i researched and find out I need SelectListItem in my Controller and than i tried to implement it in my Controller,but i got some errors :) as usually i implement DropDown in my Razor View (static) , but this time which is first time :) i want implement in my Controller so it becomes dynamic.
Can anyone point me in right direction :)
Thanks in advance :)
Controller:
//DropDown
public List<VMRMA.NameStatusDropDown> GetStatusForDropDown()
{
List<VMRMA.NameStatusDropDown> result = new List<VMRMA.NameStatusDropDown>();
var obj = db.RMAStatus.Select(u => u).ToList();
if (obj != null && obj.Count() > 0)
{
foreach (var data in obj)
{
VMRMA.NameStatusDropDown model = new VMRMA.NameStatusDropDown();
model.Status = data.Status;
model.ID = data.ID;
result.Add(model);
}
}
return result;
}
//Dropdown runs in this Action
public ActionResult RMA ()
{
VMRMA model = new VMRMA();
model.NameStatusDropDowns = GetStatusForDropDown();
//RMA query and some an other stuff
return View(model);
}
ViewModel:
public class VMRMA
{
public List<NameStatusDropDown> NameStatusDropDowns { get; set; }
//DropDown
public class NameStatusDropDown
{
public NameStatusDropDown()
{
}
public NameStatusDropDown(int ID, string Status)
{
this.ID = ID;
this.Status = Status;
}
public int ID { get; set; }
public string Status { get; set; }
}
}
View:
#using ModelNamespace.Models
#model VMRMA
<form>
<div class="form-group">
<label class="form-control-label">Select a status</label>
<br />
<select>
<option>Select</option>
#foreach (var item in Model.NameStatusDropDowns)
{
<option value="#item.ID">#item.Status</option>
}
</select>
</div>
<div class="form-group">
<input type="submit" value="Send data" class="btn btn-primary">
</div>
</form>
HTML Markup:
<div class="form-group">
<label class="form-control-label">Select a status</label>
<br>
<select>
<option>Select</option>
<option value="1">Sendt</option>
<option value="2">Under behandling</option>
<option value="3">Blive behandlet</option>
<option value="4">Modtaget</option>
</select>
</div>
This two Post helped me out to solve the problem and Thanks to #Stephen Muecke with his good post, Which is wroted Here and Thanks to this post with great explanation, which is wroted Here.
Here is what i did , maybe it helped someone one day :) :
Add To Property to my View Model :
public class VMRMA
{
public List<SelectListItem> Status { set; get; }
public int? SelectedStatus { set; get; }
}
Change my ActionResult to :
public ActionResult RMA (int Id)
{
VMRMA model = new VMRMA();
model.Status = new SelectList(DatabaseNameSpace.RMAStatus, "ID",
"Status").ToList();
//some an other stuff
return View(model);
}
and than change my View to :
#Html.DropDownListFor(s => s.SelectedStatus, Model.Status, "- Select -", new { #class = "form-control" })
Controller:
ViewBag.Statuses= new SelectList(_context.RMAStatus
.Select(item => new { value = item.Id, text = item.Status}), "value", "text", selectedId);
View:
#Html.DropDownListFor(x => x.StatusId, ViewBag.Statuses as SelectList, "- please select -")
Create a partial view as this:
#model MyApp.Models.MyClass
#{
Layout = null;
}
#*#Html.Partial("ActionMethod", "Controller", new ViewDataDictionary { { "Name", "TestName" } })*#
#Html.DropDownList((String)TempData["Name"], new SelectList( ViewBag.Specialities,"Value","Text"),
new { #class = "form-control", #multiple="multiple" });
Then in your controller
List<MyClass> lstSpecialities =
ViewBag.Specialities = lstSpecialities; // Now it is available for the view
Last step, load your view using #Html.RenderAction()

Categories