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
Related
I have a form in a razor page with a field that has remote validation..
. . .
<div class="form-group">
<label class="control-label" asp-for="ReportViewModel.ExecSql"></label>
<textarea class="form-control" asp-for="ReportViewModel.ExecSql" ></textarea>
<span asp-validation-for="ReportViewModel.ExecSql" class="text-danger"></span>
</div>
. . .
The field in the ReportViewModel is defined as:
. . .
[Remote(action: "VerifySql", controller: "ReportService", HttpMethod = "POST")]
public string ExecSql { get; set; }
To call the action below:
[AcceptVerbs("Post")]
public IActionResult VerifySql(string ExecSql)
{
if (!Repository.VerifySql(ExecSql))
return Json("Sql is not valid (Select statements only are allowed)");
return Json(data: true);
}
The razor page cs file:
public class AddReportModel : PageModel
{
private readonly IIntegrityReportRepository _repository;
private readonly IMapper _mapper;
public AddReportModel(IIntegrityReportRepository repository, IMapper mapper)
{
_repository = repository;
_mapper = mapper;
}
[TempData]
public string ConfirmationMessage { get; set; }
[BindProperty]
public IntegrityReportViewModel ReportViewModel { get; set; }
. . .
This calls the action but ExecSql is always null. This is because when I look at the request, the form is posting.. ReportViewModel.ExecSql. Which I cannot pick up from my VerifySql action method.
I have tried adding a name to the cshtml field:
<textarea class="form-control" asp-for="ReportViewModel.ExecSql" name="ExecSql" ></textarea>
This then does bind the field and pass the value, however the client side validation does not work when it is passed back.
I can get it working by defining ANOTHER field in the cs razor page file e.g.
[Remote(action: "VerifySql", controller: "ReportService", HttpMethod = "POST")]
[BindProperty]
public string ExecSqlField { get; set; }
And then changing the cshtml to:
<div class="form-group">
<label class="control-label" asp-for="ExecSqlField"></label>
<textarea class="form-control" asp-for="ExecSqlField"></textarea>
<span asp-validation-for="ExecSqlField" class="text-danger"></span>
</div>
However this feels wrong and I am then duplicating the viewmodel which I need to share across another pages. Is there anyway to pick up the field in the original viewmodel in my action method by somehow getting the Verify action method to access ReportViewModel.ExecSql?
Many thanks for any help
Just wanted to add that #Baseless and #Breno did have the correct answer.
My property was as such:
[Display(Name = "Access Code")]
[Remote("CheckForGroup", "Users", "Admin", HttpMethod = "Post")]
public string AccessCode { get; set; }
and it only worked when I added this to my controller:
public IActionResult CheckForGroup([Bind(Prefix = "Input.AccessCode")] string accessCode)
Thank you guys!
Afte banging heads a lot, I found a workaround. Not the most elegant but it is doing the job right now.
Just add [Bind(Prefix = "ReportViewModel.ExecSql")] to your validation method parameter.
You can revert the changes and resume using the "Model.Field" form pattern.
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>
I am having a problem returning values to the controller when using a ViewModel.
For clarity I have simplified the code below where the original has many more fields.
When the page is loaded, the value in the hidden field is as expected. However when the form is submitted the value in the field is not being sent and instead I get an ArgumentNullException.
Please can you advise on what I am doing wrong.
View
#model Project.Models.SCView
#using (Html.BeginForm("ScorecardEdit"))
{
#Html.AntiForgeryToken()
#Html.ValidationSummary(true)
#Html.HiddenFor(model => model.FV.ID)
<input type="submit" value="Save" />
}
Model
public class FixView
{
public int ID { get; set; }
[DisplayFormat(DataFormatString = "{0:ddd dd/MM/yyyy}")]
public DateTime MatchDate { get; set; }
}
public class SCView
{
public FixView FV { get; set; }
public SCView()
{
this.FV = new FixView();
}
}
Controller
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult ScorecardEdit(SCView ReturnSC)
{
}
The code that you have should be working as MVC should be able to map your FV.ID property as expected as the HiddenFor() helper will generate the proper names to handle mapping it :
Using the same code that you provided, the debugger demonstrated the following after submitting the form :
The issue here sounds like you have a few other properties, possibly collection-based ones that use the DropDownListFor() helpers that have collections which are not posted to the server, so when you attempt to use the model you have to render populate one of those helpers, you are getting your ArgumentNullException.
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.
I have the following code in my view
<input class="form-control" type="text" name="dev" value="foobar" />
In the controller I have
public ActionResult BobLobLaw(string dev)
The above works well. But then I changed my view model, now my view code becomes like
<input class="form-control" type="text" name="arrested.dev" value="foobar" />
How should I name my action param so that it will bind to the new input name ("arrested.dev") ?
Additionally, looking on how to do this for enum type as well. e.g.
<% Html.DropDownList("arrested.devEnum", SelectList) %>
public ActionResult BobLobLaw2(suitEnum arrested.devEnum)
The modelbinder interprets a . to mean a property on an object. So you would need some object with a property of dev as a parameter:
public class Arrested
{
public string dev { get; set; }
}
Then:
public ActionResult BobLobLaw(Arrested arrested)
{
...
}