This is my code I have setup with the latest fluent validation library.
public class ReleaseViewModelValidator : AbstractValidator<ReleaseViewModel>
{
public ReleaseViewModelValidator() {
RuleFor(r => r.Name).NotEmpty().Length(1, 30).WithMessage("Name must not be longer than 30 chars.");
}
}
[FluentValidation.Attributes.Validator(typeof(ReleaseViewModel))]
public class ReleaseViewModel { public int ReleaseId { get; set; }
[Remote("ReleaseExists", "Release", ErrorMessage = "This name already exists.")]
public string Name { get; set; }
public DateTime CreatedAt { get; set; } }
GlOBAL.ASAX:
FluentValidationModelValidatorProvider.Configure();
VIEW:
model ILMD.Web.Models.ReleaseViewModel #* Remote Validation*# <script src="#Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")" type="text/javascript"></script>
#using (Html.BeginForm("Create", "Release"))
{
<p class="editor-label">#Html.LabelFor(model => model.Name)</p>
<p class="editor-field">#Html.EditorFor(model => model.Name)</p>
<p class="editor-field">#Html.ValidationMessageFor(model => model.Name)</p>
}
All my unit tests for the ReleaseViewModelValidator show green light. Fine.
But less cool is that entering some live data like 31 chars or entering nothing I do not see any client side error message.
Do I still have to include something in my partial view? The ModelState is also not correct its always true.
Try this
[FluentValidation.Attributes.Validator(typeof(ReleaseViewModelValidator))]
Because in the FluentValidation attributes validator you have specify the actual validator (ReleaseViewModelValidator) not the ReleaseViewModel
Related
im building some dynamic form generator in blazor and i have issue with this part
#using Microsoft.AspNetCore.Components.CompilerServices
#using System.Text.Json
#using System.ComponentModel.DataAnnotations
#typeparam Type
<EditForm Model="#DataContext" OnValidSubmit="OnValidSubmit">
<DataAnnotationsValidator/>
#foreach (var prop in typeof(Type).GetProperties())
{
<div class="mb-3">
<label for= "#this.idform.ToString()_#prop.Name">#Label(#prop.Name) : </label>
#CreateStringComponent(#prop.Name)
#if (ShowValidationUnderField)
{
<ValidationMessage For = "#(()=> #prop.GetValue(DataContext))"></ValidationMessage>
}
</div>
}
#code {
[Parameter] public Type? DataContext { get; set; }
[Parameter]
public EventCallback<Type> OnValidSubmitCallback { get; set; }
[Parameter]
public bool ShowValidationSummary { get; set; } = false;
[Parameter]
public bool ShowValidationUnderField { get; set; } = true;
}
so i get this error
'provided expression contains a InstanceMethodCallExpression1 which is not supported.'
its because of
#(()=> #prop.GetValue(DataContext))
is there any other way that i can do that 'properly' ? or via builder?
thanks and regards !
ok i finaly found something similar somwhere and modified a bit and it works
so for future searchers:
#if (ShowValidationUnderField)
{
#FieldValidationTemplate(#prop.Name)
}
and in code:
public RenderFragment? FieldValidationTemplate(string fld) => builder =>
{
PropertyInfo? propInfoValue = typeof(ContextType).GetProperty(fld);
var access = Expression.Property(Expression.Constant(DataContext, typeof(ContextType)), propInfoValue!);
var lambda = Expression.Lambda(typeof(Func<>).MakeGenericType(propInfoValue!.PropertyType), access);
builder.OpenComponent(0, typeof(ValidationMessage<>).MakeGenericType(propInfoValue!.PropertyType));
builder.AddAttribute(1, "For", lambda);
builder.CloseComponent();
};
regards
I think there is a typo:
#(()=> #prop.GetValue(DataContext)) should be #(()=> prop.GetValue(DataContext))
(the second # symbol is not good here)
Can you try it please?
ValidationMessage expects an expression that it can decompose to get the property name. It uses the Model it gets from the cascaded EditContext and the property from the For to query the ValidationMessageStore on the EditContext for any messages logged against the Model/Property.
You get the error because the For isn't in the correct format.
That's the problem with building Dynamic forms: you also need to build the infrastructure that goes with them.
This is the relevant code in the Blazor repository - https://github.com/dotnet/aspnetcore/blob/66104a801c5692bb2da63915ad877d641c45cd42/src/Components/Forms/src/FieldIdentifier.cs#L91 (good luck!)
I have an a href link to a page which adds a parameter to the link for example:
tsw/register-your-interest?Course=979
What I am trying to do is to extract the value in Course i.e 979 and display it in the view. When attempting with the below code, I only return with 0 rather than the course value expected. ideally I'd like to avoid using routes.
Here is the view:
<div class="contact" data-component="components/checkout">
#using (Html.BeginUmbracoForm<CourseEnquiryPageSurfaceController>("PostCourseEnquiryForm", FormMethod.Post, new { id = "checkout__form" }))
{
//#Html.ValidationSummary(false)
#Model.Course;
}
And my controller:
public ActionResult CourseEnquiry(string Course)
{
var model = Mapper.Map<CourseEnquiryVM>(CurrentContent);
model.Course = Request.QueryString["Course"];
return model
}
This is the View Model:
public class CourseEnquiryVM : PageContentVM
{
public List<OfficeLocation> OfficeLocations { get; set; }
public string Test { get; set; }
public string Course { get; set; }
public List<Source> SourceTypes { get; set; }
}
SOLUTION:
After some research and comments I've adjusted the code to the below which now retrieves the value as expected
#Html.HiddenFor(m => m.Course, new { Value = #HttpContext.Current.Request.QueryString["Course"]});
Thanks all
Based on the form code you provided you need to use #Html.HiddenFor(m => m.Course) instead of just #Model.Course. #Model.Course just displays the value as text instead of building a input element that will be sent back to your controller.
If your problem is with a link prior to the view you referenced above, here's what I'd expect to work:
View with link:
#model CourseEnquiryVM
#Html.ActionLink("MyLink","CourseEnquiry","CourseController", new {course = #Model.Course}, null)
CourseController:
public ActionResult CourseEnquiry(string course)
{
// course should have a value at this point
}
In your view, you are only displaying the value of Course.. which isn't able to be submitted. You need to incorporate the value of course with a form input element (textbox, checkbox, textarea, hidden, etc.).
I would highly suggest using EditorFor or Textboxfor, but because your controller action is expecting just a string parameter you could just use Editor or TextBox.
#using (Html.BeginUmbracoForm<CourseEnquiryPageSurfaceController>("PostCourseEnquiryForm", FormMethod.Post, new { id = "checkout__form" }))
{
//#Html.ValidationSummary(false)
#Html.TextBox(Model.Course, null, new { #class = "form-control"});
<input type="submit" value="Submit" />
}
Then you should just be able to do this in your controller:
public ActionResult CourseEnquiry(string course) // parameter variables are camel-case
{
var model = Mapper.Map<CourseEnquiryVM>(CurrentContent);
if(!string.IsNullOrWhiteSpace(course))
model.Course = course;
return model;
}
Let me know if this helps.
I have a partial view that uses a list (CheckBoxListModel) of classes (CheckBoxModel) with a string and bool to create a list of checkboxes. The code works to create the checkboxes and sends the selected ones back to the controller when the page posts. I am trying to find a way to make my partial reusable. As you can see in the code I send the partial the full Model and this works to get the updated checkboxes when the page posts. I tried to send my Model's CheckBoxListModel, but it does not work because when it creates the checkboxes the name is incorrect. I would like to reuse the partial by sending it a CheckBoxListModel so I do not have to create a separate partial every time I need a set of checkboxes.
I tried to change_CheckBoxListPartial.cshtml to
#model MySite.Models.ViewModels.CheckBoxListModel
...
#Html.EditorFor(x => x.CheckBoxes)
...
but without the clmReturnOptions the checkbox names end up as name="CheckBoxes[0].isChecked" instead of name="clmReturnOptions.CheckBoxes[0].isChecked" so they are not updated in the Model when the page posts and gets back to the controller.
I have been looking at: http://haacked.com/archive/2008/10/23/model-binding-to-a-list.aspx/ but still can't seem to get the checkboxes to work without sending the entire model to my partial.
CheckBoxListModel.cs
public class CheckBoxListModel: ICheckBoxList
{
public IList<CheckBoxModel> CheckBoxes { get; set; }
public string CheckBoxListTitle { get; set; }
public CheckBoxListModel()
{
}
}
public class CheckBoxModel
{
public string CheckBoxName { get; set; }
public string DisplayName { get; set; }
public bool isChecked { get; set; }
public CheckBoxModel()
{ }
public CheckBoxModel(string checkboxname, string displayname, bool ischecked)
{
CheckBoxName = checkboxname;
DisplayName = displayname;
isChecked = ischecked;
}
}
public interface ICheckBoxList
{
IList<CheckBoxModel> CheckBoxes { get; set; }
string CheckBoxListTitle { get; set; }
}
ReportFilterViewModel.cs
public class ReportFilterViewModel
{
public ReportFilterViewModel()
{
clmReturnOptions = new CheckBoxListModel();
}
public CheckBoxListModel clmReturnOptions { get; set; }
}
filters.cshtml <-- this is where the partial is called
#model MySite.Areas.Reports.Models.ViewModels.ReportFilterViewModel
...
#if (Model.Filters.IsReturnsOptionsAvailable)
{
Html.RenderPartial("_CheckBoxFilterPartial", Model.clmReturnOptions);
}
...
_CheckBoxFilterPartial.cshtml
#model MySite.Areas.Reports.Models.ViewModels.ICheckBoxList
#{
Layout = null;
}
<!DOCTYPE html>
<html>
<head>
<title>Returns Options</title>
</head>
<body>
<div class="col-md-4">
<div class="plm prm ptm pbm configureCellSplitBG configureCellSplitBG-outline mtm">
<div class="row mlm mrm">
<h6>#Model.CheckBoxListTitle</h6>
</div>
#Html.EditorFor(x => x.CheckBoxes)
</div>
</div>
</body>
</html>
CheckBoxModel.cshtml
#model MySite.Areas.Reports.Models.ViewModels.CheckBoxModel
<div class="row mlm mrm">
<div class="form-group">
<label class="checkbox">
#Html.CheckBoxFor(x => x.isChecked, new { #data_toggle = "checkbox" })
#Html.LabelFor(x => x.CheckBoxName, Model.DisplayName)
#Html.HiddenFor(x => x.CheckBoxName)
</label>
</div>
</div>
UPDATE
When I view source I can see that the CheckBox names are still: name="CheckBoxes[0].isChecked"
So when the model gets back to the controller the list is null
1 other change I made was moving the CheckBoxListModel.cs from MySite.Models.ViewModels to MySite.Areas.Reports.Models, since everything else is under the reports.models.
The problem seems to be the partial view. If I put #Html.EditorFor(x => x.clmReturnOptions.CheckBoxes) in my main page the checkboxes are created with the full name and are updated correctly. As soon as i tried to use the EditorFor in the partial view the checkbox name changes and the link to them back to the Model breaks. I would like to have this in a partial view so I do not have to add all the ui formating everywhere i want a checkbox list.
I have updated the above code
You need to pass the prefix to the partial view so the elements are correctly named
#if (Model.Filters.IsReturnsOptionsAvailable)
{
Html.RenderPartial("_CheckBoxFilterPartial", Model.clmReturnOptions, new ViewDataDictionary
{
TemplateInfo = new System.Web.Mvc.TemplateInfo { HtmlFieldPrefix = "clmReturnOptions" }
})
}
You can also write a custom html helper to make this a little easier
public static MvcHtmlString PartialFor<TModel, TProperty>(this HtmlHelper<TModel> helper, Expression<Func<TModel, TProperty>> expression, string partialViewName)
{
string name = ExpressionHelper.GetExpressionText(expression);
object model = ModelMetadata.FromLambdaExpression(expression, helper.ViewData).Model;
var viewData = new ViewDataDictionary(helper.ViewData)
{
TemplateInfo = new System.Web.Mvc.TemplateInfo { HtmlFieldPrefix = name }
};
return helper.Partial(partialViewName, model, viewData);
}
and use as
#Html.PartialFor(m => m.clmReturnOptions, "_CheckBoxFilterPartial")
Create an interface that provides access to the IList<CheckBoxModel>
public interface ICheckBoxList
{
IList<CheckBoxModel> CheckBoxes { get; set; }
}
Have CheckBoxListModel implement that interface
public class CheckBoxListModel:ICheckBoxList
{...
Your _CheckBoxListPartial.cshtml partial view will use the new interface as its model
#model MySite.Areas.Reports.Models.ViewModels.ICheckBoxList
and change your EditorFor to
#Html.EditorFor(x => x.CheckBoxes)
I couldn't find the code in your question that showed how you are including the _CheckBoxListPartial partial view, but you would simply pass the clmReturnOptions property of the ViewModel (ReportFilterViewModel or otherwise) instead of the entire model.
And you should be good to go.
I have an Ajax.BeginForm() in a partial view that contains a CheckBox for a bool value. The model is as follows;
public class ViewBusinessAdd
{
[Required(ErrorMessage="Name must be supplied")]
[Display(Name = "Business Name")]
public string Name { get; set; }
[Required(ErrorMessage = "Contact must be supplied")]
[Display(Name = "Business Contact")]
public string Contact { get; set; }
[Display(Name = "Phone Number")]
public string Number { get; set; }
public string Postcode { get; set; }
public Dictionary<string, string> States { get; set; }
public string AddressRegion { get; set; }
public bool IsFacebookPost { get; set; }
public List<RecommendationViewAttribute> Attributes { get; set; }
}
The CheckBox is rendered using the Html helpers;
<div class="control-group">
<label class="control-label">
#Html.LabelFor(m => m.IsFacebookPost, "Post recommendation")
<img src="~/Content/images/f_logo.png" alt="Facebook" />
</label>
<div class="controls">
#Html.CheckBoxFor(m => m.IsFacebookPost)
</div>
</div>
This produces the following HTML when rendered;
<input data-val="true" data-val-required="The IsFacebookPost field is required." id="IsFacebookPost" name="IsFacebookPost" type="checkbox" value="true" /><input name="IsFacebookPost" type="hidden" value="false" />
When submitting the form with, it produces this error in Chrome;
Uncaught SyntaxError: Unexpected token u
If I remove the CheckBox the form submits without any error. If I convert this to a non-Ajax form, it also submits but that's not going to work with the page design unfortunately.
I'm absolutely stumped on this - I even changed this to a RadioButton and the same behavior exists. Does anyone have any ideas?
Edit: Forgot to add it's a Javascript error.
Edit: The error is coming from jQuery library on the return below;
parseJSON: function( data ) {
// Attempt to parse using the native JSON parser first
if ( window.JSON && window.JSON.parse ) {
return window.JSON.parse( data );
}
But this only happens if I use the Razor HTML helpers to generate the checkboxes.
I got bit by this and burned about 6 hours trying to figure out what the issue was. According to the Jquery developers this is intended behavior in 1.9.1. If you use the jQuery Migrate 1.1.1 plugin everything should work, other than the console warnings which I think can be turned off.
http://bugs.jquery.com/ticket/13412
See the bug report, which isn't actually a bug :)
Well it seems there was a bug introduced with jQuery 1.9.1. I've downgraded to 1.8.3 and the Razor helpers for the Checkboxes now work correctly. Steps to downgrade if anyone is interested;
Uninstall-Package jQuery -force
Install-Package jQuery -version 1.8.3
I am trying to understand how I should validate on the client sections of my MVC3 page independently and have come up with a simplyfied version of what I am trying to achieve.
If I use one form:
Pros: When I submit back to the "PostData" controller method I receive all data contained within the form. In this case both values "name" and "description", which means that I can instantiate "PersonHobbyModel" and assign the data I have received. I can either store in the database or I can return the same view.
Cons: I cant validate independently. So if "name" isn't completed and I complete "description" I can still submit the page. (This is a simplyfied version of what I am trying to do and I would have more fields than just "name" and "description")
With two forms:
Pros: I can validate independently.
Cons: The controller method only receives the subitted forms data which, in this case either "Persons name" or "Hobby description" which means that I can't recreate a full instance of "PersonHobbyModel".
This is the model:
public class Person {
[Display(Name = "Person name:")]
[Required(ErrorMessage = "Person name required.")]
public string Name { get; set; }
}
public class Hobby {
[Display(Name = "Hobby description:")]
[Required(ErrorMessage = "Hobby description required.")]
public string Description { get; set; }
}
public class PersonHobbyModel {
public PersonHobbyModel() {
this.Person = new Person();
this.Hobby = new Hobby();
}
public Person Person { get; set; }
public Hobby Hobby { get; set; }
}
This is the controller:
public class PersonHobbyController : Controller
{
//
// GET: /PersonHobby/
public ActionResult Index()
{
var model = new PersonHobbyModel();
return View(model);
}
public ActionResult PostData(FormCollection data) {
var model = new PersonHobbyModel();
TryUpdateModel(model.Person, "Person");
TryUpdateModel(model.Hobby,"Hobby");
return View("Index", model);
}
}
This is the view:
#model MultipleFORMStest.PersonHobbyModel
#{
ViewBag.Title = "Index";
}
<h2>
Index</h2>
#using (Html.BeginForm("PostData", "PersonHobby")) {
<div>
#Html.LabelFor(model => model.Person.Name)
#Html.TextBoxFor(model => model.Person.Name)
#Html.ValidationMessageFor(model => model.Person.Name)
<input type="submit" value="Submit person" />
</div>
}
#using (Html.BeginForm("PostData", "PersonHobby")) {
<div>
#Html.LabelFor(model => model.Hobby.Description)
#Html.TextBoxFor(model => model.Hobby.Description)
#Html.ValidationMessageFor(model => model.Hobby.Description)
<input type="submit" value="Submit hobby" />
</div>
}
UPDATE 1
I didnt mention, as I wanted to keep the question as simple as possible, but for one of the sections I am using "jquery ui dialog". I initially used a DIV to define the dialog, which I had inside my main form. This would of caused one problem as I wouldn't have been able to validate on the client the "JQuery dialog form" independently from the rest of the form.
Saying this jquery did removed the "div jquery ui dialog" from the main form which made me include the dialog in it's own form. For this reason I have ended up with two forms. The advantage is that I can now independently validate the "jquery dialog ui form".
But I am confused as to how should I handle on the server data submited from various forms on the client as there is a chance that the user has JS disabled. If I submit from one form I can't access the data in other forms.
UPDATE 2
Thanks for the replies. I believe I do need two forms and two entities as I want to validate them independently on the client, (apart from being kind of forced to by "Jquery UI Dialog"). For instance if I have, instead of one hobby I have a list of hobbies, which I could posible display in a grid in the same view. So I could not fill in the person name, but continue to add hobbies to the grid, If I do not complete the hobby description I'd get a validation error. (Sorry as I should of included both of my updates in the initial question but for the purpose of clarity I wanted to keep it as simple as posible)
From my perspective, you have a single view model that corresponds to two entity models. In your place I would use a single form and validate the view model and not really think about it as two (dependent) entities. Receive back the view model in your action, instead of a generic form collection, and use model-based validation via data annotation attributes. Once you have a valid, posted model you can then translate that into the appropriate entities and save it to the database.
Model
public class PersonHobbyViewModel {
[Display(Name = "Person name:")]
[Required(ErrorMessage = "Person name required.")]
public string Name { get; set; }
[Display(Name = "Hobby description:")]
[Required(ErrorMessage = "Hobby description required.")]
public string Description { get; set; }
}
Controller
public class PersonHobbyController : Controller
{
//
// GET: /PersonHobby/
[HttpGet] // mark as accepting only GET
public ActionResult Create() // Index should probably provide some summary of people and hobbies
{
var model = new PersonHobbyViewModel();
return View(model);
}
[HttpPost] // mark as accepting only POST
public ActionResult Create(PersonHobbyViewModel model) {
if (ModelState.IsValid) {
var person = new Person { Name = model.Name };
var hobby = new Hobby { Description = model.Description };
person.Hobbies = new List<Hobby> { hobby };
db.Persons.Add( person );
db.SaveChanges();
}
return RedirectToAction( "details", new { id = person.Id } ); // view the newly created entity
}
}
View
#model MultipleFORMStest.PersonHobbyViewModel
#{
ViewBag.Title = "Create";
}
<h2>
Create</h2>
#using (Html.BeginForm("Create", "PersonHobby")) {
<div>
#Html.LabelFor(model => model.Person.Name)
#Html.TextBoxFor(model => model.Person.Name)
#Html.ValidationMessageFor(model => model.Person.Name)
<input type="submit" value="Submit person" />
</div>
<div>
#Html.LabelFor(model => model.Hobby.Description)
#Html.TextBoxFor(model => model.Hobby.Description)
#Html.ValidationMessageFor(model => model.Hobby.Description)
<input type="submit" value="Submit hobby" />
</div>
}
I think your ViewModel should be only only specific to that view you are representing. In this case, i would use a ViewModel like this
public class AddPersonHobbyViewModel
{
[Required]
[Display (Name="Person Name")]
public string PersonName { set;get;}
[Required]
[Display (Name="Hobby Description")]
public string HobbyDescription { set;get;}
}
And in my PostData ActionMethod, I will check for Model Validation
[HttpPost]
public ActionResult PostData(AddPersonHobbyViewModel objVM)
{
if(ModelState.IsValid)
{
// Everything is fine. Lets save and redirect to another get View( for PRG pattern)
}
return View(objVm);
}
And you use only one Form in your View which is strongly typed to AddPersonHobbyViewModel
#model AddPersonHobbyViewModel
#using (Html.BeginForm("PostData","Person"))
{
#Html.TextBoxFor(m=>m.PersonName)
#Html.ValidationMessageFor(m => m.PersonName)
#Html.TextBoxFor(m=>m.HobbyDescription )
#Html.ValidationMessageFor(m => m.HobbyDescription )
<input type="submit" value="Save" />
}