HTTP get request possible without passing very long string to URL? - asp.net-core

What is the most efficient way to pass a very long GET request? I see a similar question was posted here: Very long http get request which suggests using a post request instead of get? Why is this?
As an example, I have an application that includes 4 multiselectlists and a dropdown. The user can select several options to filter a table down and display the results. Currently this is included in my onget method by building a string in the URL and then running linq queries to display the results based on the user's selections. It works, but it seems very inefficient to me to pass such a long URL. Isn't there a better way to do this with model binding?
.cshtml file:
<select multiple class="form-control" name="CurSelDepts" asp-items="Model.DeptList" asp-for="SelDepts"></select>
<select multiple class="form-control" name="CurSelTechs" asp-items="Model.TechOneList" asp-for="SelTechs"></select>
<select multiple class="form-control" name="CurSelTech2s" asp-items="Model.TechTwoList" asp-for="SelTech2s"></select>
<select multiple class="form-control" name="CurSelRoles" asp-items="Model.RoleList" asp-for="SelRoles"></select>
<select class="form-control col-md-6" name="CurSelEmp" asp-items="Model.EmployeeList" asp-for="SelEmp">
<option disabled selected style="display:none">--select--</option>
</select>
<input formmethod="get" type="submit" value="Search" class="btn btn-primary btn-sm" id="searchbtn" />
.cs file:
public MultiSelectList DeptList { get; set; }
public MultiSelectList TechOneList { get; set; }
public MultiSelectList TechTwoList { get; set; }
public SelectList EmployeeList { get; set; }
public MultiSelectList RoleList { get; set; }
public int SelEmp { get; set; }
public int SelNewEmp { get; set; }
public int[] SelRoles { get; set; }
public int[] SelDepts { get; set; }
public int[] SelTechs { get; set; }
public int[] SelTech2s { get; set; }
public async Task OnGetAsync(int[] selRoles, int[] curSelRoles, int selEmp, int curSelEmp, int[] selDepts, int[] curSelDepts, int[] selTechs, int[] curSelTechs, int[] selTech2s, int[] curSelTech2s)
{
DeptList = new MultiSelectList(_context.ppcc_deptCds, "Id", "dept_cd", SelDepts);
TechOneList = new MultiSelectList(_context.ppcc_techCds, "Id", "tech_cd", SelTechs);
TechTwoList = new MultiSelectList(_context.ppcc_techTwoCds, "Id", "tech_cd_two", SelTech2s);
RoleList = new MultiSelectList(_context.ppcc_roles, "Id", "role_nm", SelRoles);
EmployeeList = new SelectList(_context.employees, "Id", "employee_nm", SelEmp);
SelEmp = curSelEmp;
SelDepts = curSelDepts;
SelTechs = curSelTechs;
SelTech2s = curSelTech2s;
SelRoles = curSelRoles;
IQueryable<ppcc_matrix> ppcc_matrixIQ = from s in _context.ppcc_matrices select s;
if (curSelDepts.Any()) {ppcc_matrixIQ = ppcc_matrixIQ.Where(s => curSelDepts.Contains(s.ppcc_deptCdId));}
if (curSelTechs.Any()) {ppcc_matrixIQ = ppcc_matrixIQ.Where(s => curSelTechs.Contains(s.ppcc_techCdId));}
if (curSelTech2s.Any()) {ppcc_matrixIQ = ppcc_matrixIQ.Where(s => curSelTech2s.Contains(s.ppcc_techTwoCdId));}
if (curSelRoles.Any()) {ppcc_matrixIQ = ppcc_matrixIQ.Where(s => curSelRoles.Contains(s.ppcc_roleId));}
if (curSelEmp != 0) { ppcc_matrixIQ = ppcc_matrixIQ.Where(s => s.employeeId.Equals(curSelEmp)); }
}

Model Binding works with GET requests as well as POST requests. You just need to ensure that the public properties are decorated with the BindProperty attribute with SupportsGet = true (https://www.learnrazorpages.com/razor-pages/model-binding#binding-data-from-get-requests):
[BindProperty(SupportsGet=true)]
public Type MyProperty { get; set; }
As to very long URLs, there is a limit to the length of a GET request. POST requests also have limits imposed by servers, but it is very much larger by default. It needs to be to cater for file uploads, for example.

Related

Problem when submit a form with select tag helper asp.net core 6

I want to use select tag helper to choose a role for creating account in my web app.
The following is my code
[HttpGet]
public ActionResult Create()
{
var model = new AccountCreateViewModel()
{
Roles = new SelectList(_roleManager.Roles, "Id", "Name").ToList()
};
return View(model);
}
The following is the code in view of the select.
<div class="form-floating">
<select asp-for="RoleId" asp-items="#Model.Roles" class="form-select"></select>
<label asp-for="RoleId"></label>
<span asp-validation-for="RoleId" class="text-danger"></span>
</div>
<input type="submit" class="w-100 btn btn-lg btn-primary" />
The following is my model
public class AccountCreateViewModel
{
public RegisterModel.InputModel Input { get; set; } = new();
[StringLength(50)]
[Required]
public string FullName { get; set; }
[DataType(DataType.Date)]
public DateTime? BirthDate { get; set; } = null;
[StringLength(80)]
public string Address { get; set; }
[Required]
[Display(Name = "Role")]
public string RoleId { get; set; }
public List<SelectListItem> Roles { get; set; }
}
However, after I submit the form, then the controller check the model state, and it is invalid. I have debugged and all the fields is valid except Roles.
So, someone can give me a solution for this situation?
model state debugging
Apply [ValidateNever] attribute to remove validate of the Roles on the server side. When applied to a property, the validation system excludes that property.
public class AccountCreateViewModel
{
...
[ValidateNever]
public List<SelectListItem> Roles { get; set; }
}
Consider that a SelectList takes an object of type IEnumerable as the first argument in the constructor. Make sure that you give it an IEnumerable or List in this section:
Roles = new SelectList(_roleManager.Roles, "Id", "Name").ToList()
This code may help you:
Roles = new SelectList(_roleManager.Roles.ToList(), "Id", "Name")
If _roleManager.Roles returns an IEnumerable or List, you don't need the .ToList()

ASP.NET Core filter a list using a multi-select List Box

I have a standard razor page using the "List" template that I have added a dropdown to filter which records are shown. I used the code example from here: https://learn.microsoft.com/en-us/aspnet/core/tutorials/razor-pages/search?view=aspnetcore-3.1
Using the tutorial's "Search by Genre" section.
I have it working but would like users to be able to select multiple items from the list to use as filters. I added "multiple" to the select tag and I can select multiple items, but it only processes the first item selected.
How can I have multiple items added to the filter so that all selected values are displayed on the list?
After adding "multiple" to the select tag, you need to modify the received model MovieGenre. For example:
public class IndexModel : PageModel
{
[BindProperty(SupportsGet = true)]
public string SearchString { get; set; }
public SelectList Genres { get; set; }
[BindProperty(SupportsGet = true)]
public List<string> MovieGenre { get; set; }
public void OnGet()
{
// Transfer data here.
Genres = new SelectList(new List<Genre> {
new Genre{id=1,GenreName="Genre1"},
new Genre{id=2,GenreName="Genre2"},
new Genre{id=3,GenreName="Genre3"},
},"id", "GenreName");
}
}
public class Genre
{
public int id { get; set; }
public string GenreName { get; set; }
}
In Index.cshtml:
<form>
<select asp-for="MovieGenre" asp-items="Model.Genres" multiple>
<option value="">All</option>
</select>
<input type="submit" value="Filter" />
</form>
Then, it will get Multiple MovieGenre.
result:

How to pass List<model> to controller in MVC 4

I have 2 model : Question and Answer such as below, I want to send a List model to View, and when submit form, i submit List model to controller, but in Action UpdateQuestion a can only get the list of question but the list of answer was not. Can you explain and show me how to get list answer of each question when i submit form
public class Question
{
[Key]
public int Id { get; set; }
[ForeignKey("QuestionType")]
public int QuestionTypeId { get; set; }
public virtual QuestionType QuestionType { get; set; }
[ForeignKey("Field")]
public int FieldId { get; set; }
public virtual Field Field { get; set; }
public string Brief { get; set; }
public bool IsGroup { get; set; }
[ForeignKey("QuestionGroup")]
public int? QuestionGroupId { get; set; }
public virtual QuestionGroup QuestionGroup { get; set; }
public int Priority { get; set; }
public int Order { get; set; }
public virtual ICollection<Answer> Answers { get; set; }
}
and:
public class Answer
{
[Key]
public Int32 Id { get; set; }
[Column(TypeName = "ntext")]
[MaxLength]
public string Content { get; set; }
[ForeignKey("Question")]
public int QuestionId { get; set; }
public virtual Question Question { get; set; }
public float Mark { get; set; }
public int Priority { get; set; }
}
I have controller Index to passing a list of Question to View:
public ActionResult Index()
{
ApplicationDbContext db = new ApplicationDbContext();
var listQuestion = db.Questions.ToList();
return View(listQuestion);
}
[HttpPost]
public ActionResult UpdateQuestion(string submit, List<Question> Questions)
{
...
return RedirectToAction("Index");
}
And In View :
#model List<Question>
#{
int i = 0;
int j = 0;
}
#using (Html.BeginForm("UpdateQuestion", "TestRoom"))
{
<ul>
#foreach(var question in Model)//Question
{
<li>
#Html.Hidden("Questions["+i+"].Id", question.Id)
#{i++;}
#Html.Raw(question.Brief)
<ul>
#foreach (var answers in question.Answers)
{
<li>#Html.RadioButton("Questions["+i+"]_Answers["+j+"]",answers.Id)
#Html.Raw(answers.Content)
#{j++;}
</li>
}
#{j = 0;}
</ul>
</li>
}
</ul>
<div class="aq-button-panel">
<button type="submit" value="Finish" name="submit"><i class="icon-pencil"></i>Submit</button>
<button type="submit" value="Back" name="submit">Go Next <i class="icon-arrow-left"></i></button>
<button type="submit" value="Next" name="submit">Go Back <i class="icon-arrow-right"></i></button>
</div>
}
There are multiple issues with you code. First you cannot bind a radio button to a complex object (in your case Answer because a radio button group only posts back a single value (in your case the id value of the selected Answer). Next you loops are generating radio buttons groups that would be attempting to bind the selected answer to only the first answer which makes no sense (your setting the value of j to 0 each time). Your model needs a property to bind to (say) int SelectedAnswer.
Start by creating view models that represent what you want to display/edit in your view (add display and validation attributes as required)
public class AnswerVM
{
public int ID { get; set; }
public string Content { get; set; }
}
public class QuestionVM
{
public int ID { get; set; }
public string Brief { get; set; }
public int SelectedAnswer { get; set; }
public IEnumerable<AnswerVM> PossibleAnswers { get; set; }
}
In your get method, get your data models and map then to the view models and return IEnumerable<QuestionVM> to the view.
Next create an EditorTemplate for typeof QuestionVM (/Views/Shared/EditorTemplates/QuestionVM.cshtml)
#model QuestionVM
<li>
#Html.HiddenFor(m => m.ID)
#Html.DisplayFor(m => m.Brief)
<ul>
#foreach(var answer in Model.PossibleAnswers)
{
<li>
<label>
#Html.RadioButtonFor(m => m.SelectedAnswer, answer.ID, new { id = "" })
<span>#answer.Content</span>
</label>
</li>
}
</ul>
</li>
and in the main view
#model IEnumerable<QuestionVM>
....
#Html.BeginForm(...))
{
<ul>
#Html.EditorFor(m => m) // this will generate the correct html for each question in the collection
</ul>
<div class="aq-button-panel">
<button type="submit" ... />
...
</div>
}
and change the POST method to
[HttpPost]
public ActionResult UpdateQuestion(string submit, IEnumerable<QuestionVM> model)
The model now contains the ID of each question and the ID of the selected answer for each question.
Note that if you need to return the view because ModelState is invalid, you will need to repopulate the PossibleAnswers property of each question (your not generating a form control for each property of each Answer in each Question - and nor should you) so the PossibleAnswers property will be an empty collection when you submit the form)

Encrypted Id is not retaining back in controller

I am encrypting id to hide the raw id in query string and passing it to the controller. But the Id is not retaining back in the postback to the controller.
for eg
/Vendor/EditVendor/mELirpUhRYksFj7k8-XBcQ%3d%3d
DecryptLong() method will decrypt the above id string mELirpUhRYksFj7k8-XBcQ%3d%3d to 1
controller
public ActionResult EditVendor(string id)
{
var vendor = _vendorService.GetVendorById(id.DecryptLong());
return View(vendor);
}
[HttpPost]
public ActionResult EditVendor(Vendor vendor)
{
if (ModelState.IsValid)
{
vendor.Id -- it is always zero and not retaining back
_vendorService.EditVendor(vendor);
}
return View(vendor);
}
In view
#model Eclatech.KidsHub.Objects.Vendor
#{
ViewBag.Title = "EditVendor";
Layout = "~/Views/Shared/_Layout.cshtml";
}
<h2>Edit Vendor</h2>
#using(Html.BeginForm("EditVendor","Vendor",FormMethod.Post, new Dictionary<string, object>
{
{"class","form-horizontal"},
{"role","form"}
}))
{
<div class="form-group">
#Html.LabelFor(m => m.VendorName, new Dictionary<string, object>
{
{"class","col-sm-2 control-label"}
})
<div class="col-sm-10">
#Html.TextBoxFor(m => m.VendorName,new Dictionary<string, object>
{
{"class","form-control"}
})
</div>
</div>
#Html.HiddenFor(m => m.Id)
<input type="submit" class="btn btn-primary btn-default" value="Save" />
}
Model
public class Vendor : AuditableEntity<long>
{
public string VendorName { get; set; }
public virtual ICollection<Product> Products { get; set; }
}
public abstract class AuditableEntity<T> : Entity<T>, IAuditableEntity
{
[ScaffoldColumn(false)]
public DateTime CreatedDate { get; set; }
[MaxLength(256)]
[ScaffoldColumn(false)]
public string CreatedBy { get; set; }
[ScaffoldColumn(false)]
public DateTime UpdatedDate { get; set; }
[MaxLength(256)]
[ScaffoldColumn(false)]
public string UpdatedBy { get; set; }
}
public abstract class Entity<T> : BaseEntity, IEntity<T>
{
private static long _rowNumber;
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public virtual T Id { get; set; }
[NotMapped]
public virtual long RowNumber
{
get { return ++_rowNumber; }
}
}
The problem is that your parameter name for the EditVendor method is named id and you are returning a model that also has a property named id. When you call the EditVendor method, the value of the parameter is added to ModelState which overrides the value of property Vendor.Id. If you inspect the html generated by #Html.HiddenFor(m => m.Id) you will see that the value of the input is mELirpUhRYksFj7k8-XBcQ%3d%3d, not the value returned by DecryptLong(). When this posts back, it cannot be bound to type int so Id has its default value of zero.
You can test this by adding ModelState.Clear(); before calling GetVendorById(). This will clear the value of Id and the hidden inputs value will now be 1. To solve the problem, change the name of the parameter, for example
public ActionResult EditVendor(string vendorID)
{
var vendor = _vendorService.GetVendorById(vendorID.DecryptLong());
return View(vendor);
}

Poll System in ASP.NET MVC

I want to Display Polling in section of My Page, I have created these POCO classes for do that :
public class Polls
{
public int Id { get; set; }
public string Question { get; set; }
public bool Active { get; set; }
public IList<PollOptions> PollOptions { get; set; }
}
public class PollOptions
{
public int Id { get; set; }
public virtual Polls Polls { get; set; }
public string Answer { get; set; }
public int Votes { get; set; }
}
And I have Used below ViewModel :
public class PollViewModel
{
public int Id { get; set; }
public string Question { get; set; }
public string Answer { get; set; }
}
Then, I passed my model using above ViewModel to my View :
public ActionResult Index()
{
var poll = from p in db.Polls
join po in db.PollOptions on p.Id equals po.Polls.Id
where p.Active == true
select new PollViewModel {
Id=p.Id,
Question=p.Question,
Answer=po.Answer
};
return View(model);
}
in my View I want to display Question and Answer of my Poll, I have tried this code :
#section Polling{
#foreach (var item in Model.Polls)
{
<input type="radio" /> #item.Answer
}
}
above code works correctly but I want to display Question too, something like this :
#section Polling{
**#Model.Polls.Question**
#foreach (var item in Model.Polls)
{
<input type="radio" /> #item.Answer
}
}
How can I do that?
PS: I have one row in my Polls Table for display in Home Page
There is relationship between Polls and PollsOption. So get Polls from your db. And pass it to view. Also you already have PollsOptions that connected to to their Polls. No need to join two tables.
controller
public ActionResult Index()
{
// get active Polls
var poll = from p in db.Poll
where p.Active == true
select p;
// pass it to the view
return View(poll);
}
view
#model IEnumerable<Polls>
#section Polling{
#foreach (var question in Model)
{
<h2>#question.Question</h2>
#foreach(var answer in question.PollOptions)
{
<input type="radio" /> #answer.Answer
}
}
}