View Component with validation errors - asp.net

When i submit a form with incorrect data within a view component, my view component does not display any errors on the page.
So is it possible for a view component to return validation errors ?
View Component
<div class="card-block">
<form class="form-inline-custom" asp-controller="BragOption" asp-action="Create" method="post" role="form">
<div class="form-group">
<label asp-for="CreateBragOptionViewModel.PeriodFrom">From <span asp-validation-for="CreateBragOptionViewModel.PeriodFrom" class="alert-danger"></span></label>
<input asp-for="CreateBragOptionViewModel.PeriodFrom" class="form-control">
</div>
<div class="form-group">
<label asp-for="CreateBragOptionViewModel.PeriodTo">To <span asp-validation-for="CreateBragOptionViewModel.PeriodTo" class="alert-danger"></span></label>
<input asp-for="CreateBragOptionViewModel.PeriodTo" class="form-control">
</div>
<div class="form-group">
<button type="submit" class="btn btn-block btn-primary">START VOTING PERIOD</button>
</div>
</form>
</div>
Action Controller
[HttpPost]
public IActionResult Create(BragOptionViewModel model)
{
if (! ModelState.IsValid)
{
return View(nameof(BragManagementController.Index), model);
}
if (! _bragOptionService.IsVotingPeriodFromValid(model.CreateBragOptionViewModel))
{
ModelState.AddModelError("PeriodFrom", "The date you have entered should not be in the future");
return View(nameof(BragManagementController.Index), model);
}
if (!_bragOptionService.IsVotingPeriodToValid(model.CreateBragOptionViewModel))
{
ModelState.AddModelError("PeriodTo", "The date you have entered should not be in the past");
return View(nameof(BragManagementController.Index), model);
}
_bragOptionRepository.CreateVotingPeriod(_bragOptionService.LoadBragOption(model.CreateBragOptionViewModel));
return RedirectToAction(nameof(BragManagementController.Index), "BragManagement");
}
view models
public class CreateBragOptionViewModel
{
[DataType(DataType.Date)]
public DateTime PeriodFrom { get; set; }
[DataType(DataType.Date)]
public DateTime PeriodTo { get; set; }
}
public class BragOptionViewModel
{
public CreateBragOptionViewModel CreateBragOptionViewModel { get; set; }
[DataType(DataType.Date)]
[DisplayFormat(DataFormatString = "{MMM 0:d, yyyy}", ApplyFormatInEditMode = true)]
public DateTime VotingPeriodTo { get; set; }
[DataType(DataType.Date)]
[DisplayFormat(DataFormatString = "{MMM 0:d, yyyy}", ApplyFormatInEditMode = true)]
public DateTime VotingPeriodFrom { get; set; }
}

Haitham's comment points to the right path. Based on your code, I think that you are looking for both unobtrusive client side validation as well as a display of the validation summary in the event that the ModelState.IsValid property is false or there are programmer defined logic constraints that can't be detected until the model data reaches the controller.
In the asp.net core docs, it looks like most of this information is covered in the following link:
https://docs.asp.net/en/latest/tutorials/first-mvc-app/validation.html?highlight=validation
Client Side Unobtrusive Validation Needs both the javascript libraries and the correct HTML markup and attributes. The markup is auto-matically added in VS when you allow VS to create a strongly typed view that is bound to your model or viewmodel class.
JAVASCRIPT libraries In Order For Unobtrusive Validation
jquery.js (or min)
jquery.validate.js (or min)
jquery.validate.unobtrusive.js (or min)
HTML Markup
Spans after the input elements can display client side validation errors, in your code it would be something like adding spans with
<span asp-validation-for="<your model property>"></span>
Ex).
<label asp-for="CreateBragOptionViewModel.PeriodFrom">From <span asp-validation-for="CreateBragOptionViewModel.PeriodFrom" class="alert-danger"></span></label>
<input asp-for="CreateBragOptionViewModel.PeriodFrom" class="form-control">
<span asp-validation-for=""CreateBragOptionViewModel.PeriodFrom"></span>
Additionally, if you don't have a client side input error but you add an error to the ModelState manually after the data reaches the controller, you can display or render the model error data in a div with the validation-summary attribute inside your form when the model is returned to the view.
<form class="form-inline-custom" asp-controller="BragOption" asp-action="Create" method="post" role="form">
<div asp-validation-summary="ModelOnly"></div>

Related

asp.net core razor pages onpost set value of textbox

i am new to asp.net core, i am working with basic razor page and trying to issue onpost via submit button , then have a public property that is binded to the page get set to a specific value from the onpost. here is my code below. But when i click on the submit button the post happens but nothing gets updated? how can i get the test1 when it's set in the OnPost to "this should work" back on the page in the textbox ?
Index.cshtml
#page
#model IndexModel
#{
ViewData["Title"] = "Home page";
}
<div class="text-center">
<h1 class="display-4">Welcome</h1>
<p>Learn about building Web apps with ASP.NET Core.</p>
</div>
<form method="post">
<label>testing</label>
<input asp-for="#Model.test1" type="text" />
<br />
<br />
<input id="Submit1" type="submit" value="submit" />
</form>
then my Index.cshtml.cs
namespace WebApplication1.Pages
{
[BindProperties]
public class IndexModel : PageModel
{
private readonly ILogger<IndexModel> _logger;
public IndexModel(ILogger<IndexModel> logger)
{
_logger = logger;
}
public string test1 { get; set; }
public void OnGet()
{
}
public IActionResult OnPost()
{
test1 = "this should work";
return Page();
}
}
}
But when i click on the submit button the post happens but nothing
gets updated? how can i get the test1 when it's set in the OnPost to
"this should work" back on the page in the textbox ?
To solve this issue, you should bind value attribute to input control as follow:
<input asp-for="#Model.test1" type="text" value="#Model.test1"/>
And you can refer to this link.
Here is the test result:

How to pass model plus additional parameter from view to controller

Using ASP.NET MVC, in my view, I list some files and show them as a button. By clicking each button the corresponding file should be downloaded.
To show the list of files, I pass a model to view and when a user clicks on each of those buttons, I have to send the filename plus the original model back to the controller.
I found answers for a similar question but in my case, I don't have only one button. I have one button for each filename that I render on view.
This is the code for my view:
#using (Html.BeginForm("DownloadFile", "SharedFolder", FormMethod.Post))
{
<div class="col-sm-6">
<div class="panel panel-info">
<div class="panel-heading">Files</div>
<div class="panel-body" style="max-height:300px; height:300px; overflow-y:scroll">
#foreach (var file in Model.Files)
{
<button type="button" class="btn btn-link btn-sm" onclick="location.href='#Url.Action("DownloadFile", "SharedFolder", new { fileToDownload = file, data = Model })'">
<div class="glyphicon glyphicon-file" style="color:dodgerblue">
<span style="color:black;">#file</span>
</div>
</button>
<br />
}
</div>
</div>
</div>
}
And my controller action:
[HttpPost]
public ActionResult DownloadFile(string fileToDownload, FolderExplorerViewModel data)
{
// download the file and return Index view
return RedirectToAction("Index");
}
When I click on a file to download it, I get below error:
The resource cannot be found.
Requested URL: /SharedFolder/DownloadFile
Update:
This is my ViewModel
public class FolderExplorerViewModel
{
public int ID { get; set; }
public List<Folder> Folders { get; set; }
public List<string> Files { get; set; }
public string SelectedPath { get; set; }
}
You shouldn't store data = Model like this causes the performance and security problem.
You just store one fileToDownload value from View. After that, in Controller, You should get file by fileToDownload param.
[HttpPost]
public ActionResult DownloadFile(string fileToDownload)
{
// download the file by `fileToDownload` param here
return RedirectToAction("Index");
}
The reason why you are getting that error is because, its trying to do form post and there are no form html elements representing fileToDownload and data.
Actually, instead of using form POST you can use anchor tags invoking controller GET action (since its just download, no need to use POST here). As #Phong mentioned, using fileToDownload you probably can retrieve other information which is needed for redirection to Index.
#foreach (var file in Model.Files)
{
#file
}
and then your controller action will be:
public ActionResult DownloadFile(string fileToDownload)
{
// do the stuff
// download would return FileResult on success, not sure what you meant by RedirectToAction("Index")
// download the file and return Index view
return RedirectToAction("Index");
}
If you still wanted to send Model, then you can do that via javascript button click event with ajax.

Submit two forms to one action Asp.Net

I'm trying to submit two forms to one action. I can't figure out is it a good idea and how to do better. Each form has it own partial view and both forms using same model. I mean model is divided by forms.
Here is view model and model:
public class TestViewModel
{
public Foo Foo { get; set; }
// here is some other data for main form
}
public class Foo
{
public string Name { get; set; }
public string Street { get; set; }
}
Action method:
[HttpPost]
public ActionResult Add(Foo foo)
{
return View();
}
Main view:
#model SushiJazz.Models.ViewModels.TestViewModel
<body>
<div id="frmName">
#{Html.RenderPartial("NameFrm"); }
</div>
<div id="frmStreet">
#{Html.RenderPartial("StreetFrm"); }
</div>
<div>
<input type="submit" value="btn" id="btn-submit"/>
</div>
</body>
First form:
#model SushiJazz.Models.ViewModels.TestViewModel
#using (Html.BeginForm("Add", "Test", FormMethod.Post, new { #id="name-frm"}))
{
#Html.TextBoxFor(model => model.Foo.Name);
<input type="submit" value="name" />
}
Second form:
#model SushiJazz.Models.ViewModels.TestViewModel
#using (Html.BeginForm("Add", "Test", FormMethod.Post, new { #id="street-frm"}))
{
#Html.TextBoxFor(model => model.Foo.Street)
<input type="submit" value="street" />
}
I want to submit both forms by btn-submit click to Add action.
I mean not call Add action twice but get Foo.Name and Foo.Street filled from different forms, call Add action and pass fully filled Foo model to it.
Is it possible? Maybe there is some other ways?
I suppose you're just demonstrating a concept as it doesn't make any sense to split those small pieces into separate partial views in this case.
But just move the #using (Html.BeginForm... wrapping from each partial view to include all your mark up in the main view with containing the two #{Html.RenderPartial sections.

How can I access model property inside viewmodel using asp-for?

I have this model
public partial class MtbWarehouse
{
public int Idhwarehouse { get; set; }
public string Namewh { get; set; }
}
I implement the model in this view model
public class WarehouseViewModel
{
public MtbWarehouse Warehouse { get; set; }
public string ListAction { get; set; } = "Index";
}
And I am using WarehouseViewModel in view
#model ViewModel.warehouseviewmodel
<input type="text" asp-for="Warehouse" class="form-control" placeholder="">
How can I access Namewh using asp-for because this asp-for thing is better than using
#Html.TextBoxFor()
If you are after rendering an input text element for Namewh property of the Warehouse property of your view model, you can use the dot notation to access a child properties' property. Like this
<form asp-action="Index" asp-controller="Home">
<input type="text" asp-for="Warehouse.Namewh" />
<input type="submit"/>
</form>
This will generate an input text element with name Warehouse.Namewh and will render the value of that property if you set that from your GET action method.
VS Intellisense may still show this as invalid with red color. But when this code is executed, the tag helper will generate the correct input field with the value

Resolve asp-for in custom tag helper

I have a lot of Bootstrap inputs in my edit forms and I'm using the asp-for tag helper for model binding.
<div class="form-group">
<div class="fg-line">
<label asp-for="#Model.Name" class="control-label"></label>
<input asp-for="#Model.Name" class="form-control"/>
</div>
<span asp-validation-for="#Model.Name" class="help-block"></span>
</div>
I want to write a custom tag helper, so that I can write:
<bsinput asp-for="#Model.Name" />
...which produces the output above.
Is it possible to evaluate nested tag helpers?
I stumbled upon this question while doing some research on the same problem. This is how I resolved the problem for me :
In my case I have a colour picker that is generated with my custom tag helper. This is my class :
public class ColourPickerTagHelper : TagHelper
{
public ModelExpression AspFor { get; set; }
public List<CustomSelectItem> AspColours { get; set; }
public override void Process(TagHelperContext context, TagHelperOutput output)
{
output.TagName = "select";
string name = this.AspFor.Name;
if (!String.IsNullOrEmpty(name))
{
output.Attributes.Add("name", name);
}
output.Content.SetHtmlContent(LoadMyOptions());
output.TagMode = TagMode.StartTagAndEndTag;
}
}
And I call it like this :
<colour-picker asp-for="Form.Colour" asp-colours="Model.MyOptions" />
EDIT : I updated my answer since I found out about the ModelExpression object.

Resources