How to Provide Fluent Validation on Multi-Select Component using MudBlazor - c#

I have a Blazor app that manages a lot of form input. Every form is tied to an instance of type IncidentLog, and every UI element of the form is tied to a property of that IncidentLog instance.
In the form, we have a MudSelect component where T="Department". The MudSelect has MultiSelection="true", and the results are stored in a IEnumerable(Department) Departments property of the IncidentLog instance.
This component works totally fine, but I've tried implementing FluentValidation in the form and I'm not sure how to define the expression of the For parameter of this MudSelect component. It looks like it's expecting me to pass an object that matches T="Department", but the validation I need to run against the validator is based off of IEnumerable(Department)... I just want to check that the user has selected at least one department from the multiselect component.
Error message when trying to pass IEnumerable object to the For validator:
IncidentLogPropertiesComponent razor page - relevant blurb
<MudForm Model="#Log" #ref="#form" Validation="#(incidentLogValidator.ValidateValue)" ValidationDelay="100">
<MudCardContent Class="pt-0">
...
<MudSelect T="Department" ToStringFunc="#ConverterDepartment" Dense="true" Margin="Margin.Dense" Label="Affected Departments:" MultiSelection="true" #bind-SelectedValues="Log.Departments" Clearable>
#foreach (var department in Departments.OrderBy(o=>o.DepartmentName).ToList())
{
<MudSelectItem Value="#department"/>
}
</MudSelect>
...
</MudCardContent>
</MudForm>
IncidentLog.cs:
public class IncidentLog : Log
{
[Key]
public int IncidentID { get; set; }
....
[Write(false)]
public IEnumerable<Department> Departments { get; set; } = new HashSet<Department>();//3
....
}
IncidentLogPropertiesComponentBase
public class IncidentLogPropertiesComponentBase : OwningComponentBase<iIncidentLogRepository>, IDisposable
{
protected IncidentLogValidator incidentLogValidator = new IncidentLogValidator();
...
[Parameter]
public IncidentLog? Log
{
get
{
return (AppState.SelectedLog != null && AppState.SelectedLog.LogType.LogTypeID == 2) ? (IncidentLog)AppState.SelectedLog : null;
}
set
{
AppState.SetLog(value);
}
}
}
IncidentLogValidator:
public class IncidentLogValidator: AbstractValidator<IncidentLog>
{
public IncidentLogValidator()
{
...
RuleFor(t => t.Departments).NotEmpty().WithMessage("Must enter at least one department affected.");
}
public Func<object, string, Task<IEnumerable<string>>> ValidateValue => async (model, propertyName) =>
{
var result = await ValidateAsync(ValidationContext<IncidentLog>.CreateWithOptions((IncidentLog)model, x => x.IncludeProperties(propertyName)));
if (result.IsValid)
return Array.Empty<string>();
return result.Errors.Select(e => e.ErrorMessage);
};
}
*I didn't link my AppState state management file even though it's referenced in code above - I don't think it's relevant to the issue I'm having.
How do I provide Fluent Validation to Multi-Select Dropdowns using MudBlazor?

Related

How do I dynamically assign a value to an MVC grid based on the value from another column in the row

I have a dataset currently that has 4 columns for values lets call them odd_low, odd_high, and even_low, even_high and I want to have two columns in the grid (LOW and HIGH) and have the values set based on the value of another column which will simply be 'O' or 'E' - This column is named side
Here's a quick sample (right now the column is bound to the odd fields only)
columns.Add(model => model.ODD_LOW).Titled("Low House #").Sortable(sortable);
columns.Add(model => model.ODD_HIGH).Titled("High House #").Sortable(sortable);
columns.Add(model => model.SIDE).Titled("Side").Sortable(sortable);
My guess is that I'll need to accomplish this using a script, but I'm not sure how to dynamically access the rows and fields.
I don't see the need for a dynamic datagrid in the scenario you discribed.
When using MVC, it's a good practice to use a ViewModel object to represent data. This way, your controller is resposible for getting the data from your business / service layer and map the result to your ViewModel object.
Using this aproach you can create an object with your view's properties and just bind it to your gridview and other controlls.
Your ViewModel object would look like this:
public class MyViewModelItem {
public int LowHouse { get; set; }
public int HighHouse { get; set; }
public char Side { get; set; }
}
public class MyViewModel {
// Your other view's properties
public List<MyViewModelItem> List { get; set; }
}
And your controller like this:
public class MyController : Controller {
private readonly IMyService myService;
public MyController()
{
myService = new MyService(); // Consider Dependency Injection
}
public ActionResult Index() {
var data = myService.List();
var myModel = MapMyModel(data);
return View(myModel);
}
private MyViewModel MapMyModel(IEnumerable<YOUR_ENTITY> data) {
var myModel = new MyViewModel();
myModel.List = new List<MyViewModelItem>();
foreach (var item in data)
{
myModel.List.Add(new MyViewModelItem {
LowHouse = item.ODD_LOW,
HighHouse = item.ODD_HIGH,
Side = [your logic]
})
}
}
}
References:
Use ViewModels to manage data & organize code in ASP.NET MVC applications
ASP.NET MVC View Model Patterns

How can I reuse a DropDownList in several views with .NET MVC

Several views from my project have the same dropdownlist...
So, in the ViewModel from that view I have :
public IEnumerable<SelectListItem> FooDdl { get; set; }
And in the controller I have :
var MyVM = new MyVM() {
FooDdl = fooRepository.GetAll().ToSelectList(x => x.Id, x => x.Name)
}
So far so good... But I´m doing the same code in every view/controller that have that ddl...
Is that the best way to do that?
Thanks
I'd say that's fine to be honest, as it's only a repeat of a few lines of code. If it's really bothering you though, you could have all your controllers inherit from a BaseController (if they don't already) and store a method in there to get them all, something like:
public IEnumerable<SelectListItem> GetFoos()
{
return fooRepository.GetAll().ToSelectList(x => x.Id, x => x.Name);
}
Then in your controllers you could do:
var MyVM = new MyVM() {
FooDdl = GetFoos()
}
If your DropDownList is exactly the same the approach I would use is:
1) In your Base Controller or in a Helper class, you can create a method that returns a SelectList. That method should receive a nullabe int to get the select list with a value pre selected.
2) It is wise to cache the information you list in the DDL, to not query the database too often.
So, for (1):
public SelectList GetMyDDLData(int? selectedValue){
var data = fooRepository.GetAll().Select(x => new { Value = x.Id, Text = x.Name });
return new SelectList(data, "Id","Name", selectedValue);
}
In the view model:
var myVM = new MyVM();
myVM.DDLData = this.GetMyDDLData(null) // if it is in your BaseController.
myVM.DDLData = YourHelperClass.GetMyDDLData(null) // if it is in a helper static class
In your views:
#Html.DropDownListFor(x => x.FooProp, Model.DDLData, "Select one...")
For number (2):
private IEnumerable<YourModel> GetMyData()
{
var dataItems = HttpContext.Cache["someKey"] as IEnumerable<YourModel>;
if (dataItems == null)
{
// nothing in the cache => we perform some expensive query to fetch the result
dataItems = fooRepository.GetAll().Select(x => new YourModel(){ Value = x.Id, Text = x.Name };
// and we cache it so that the next time we don't need to perform the query
HttpContext.Cache["someKey"] = dataItems ;
}
return dataItems;
}
The "someKey" could be something specific and static is this data is the same to all users, or you can do "someKey" + User.Id if the data is specific to one user.
If your repository is an abstractin layer (not directly EntityFramework) you can place this code there.
We also use a static class :
public static class SelectLists
{
public static IList<SelectListItem> CompanyClasses(int? selected)
{
var session = DependencyResolver.Current.GetService<ISession>();
var list = new List<SelectListItem>
{
new SelectListItem
{
Selected = !selected.HasValue,
Text = String.Empty
}
};
list.AddRange(session.All<CompanyClass>()
.ToList()
.OrderBy(x => x.GetNameForCurrentCulture())
.Select(x => new SelectListItem
{
Selected = x.Id == (selected.HasValue ? selected.Value : -1),
Text = x.GetNameForCurrentCulture(),
Value = x.Id.ToString()
})
.ToList());
return list;
}
}
In the view we have nothing special :
#Html.DropDownListFor(x => x, SelectLists.CompanyClasses(Model))
And sometime we also create an EditorTemplate so it's faster to reuse like this
Model :
[Required, UIHint("CompanyClassPicker")]
public int? ClassId { get; set; }
EditorTemplate :
#model int?
#if (ViewBag.ReadOnly != null && ViewBag.ReadOnly)
{
var item = SelectLists.CompanyClasses(Model).FirstOrDefault(x => x.Selected);
if (item != null)
{
<span>#item.Text</span>
}
}
else
{
#Html.DropDownListFor(x => x, SelectLists.CompanyClasses(Model))
}
Create object with getter for your dropdown values:
public static class DropDowns
{
public static List<SelectListItem> Items {
get
{
//Return values
}
}
}
Create Razor partial:
#Html.DropDownListFor(m => "ChoosenItem", DropDowns.Items, "")
Call partial:
#Html.RenderPartial("DropDownItems")
And finally receive ChoosenItem value in controller. Simply.
I use an IModelEnricher combined with Automapper and attributes that define relationships between a type of list and select list provider. I return an entity etc using a specific ActionResult that then automaps my entity to a ViewModel and enriches with data required for select lists (and any additional data required). Also keeping the select list data as part of your ViewModel keeps your controller, model, and view responsibilities clear.
Defining a ViewModel ernicher means that anywhere that ViewModel is used it can use the same enricher to get its properties. So you can return the ViewModel in multiple places and it will just get populated with the correct data.
In my case this looks something like this in the controller:
public virtual ActionResult Edit(int id)
{
return AutoMappedEnrichedView<PersonEditModel>(_personRepository.Find(id));
}
[HttpPost]
public virtual ActionResult Edit(PersonEditModel person)
{
if (ModelState.IsValid){
//This is simplified (probably don't use Automapper to go VM-->Entity)
var insertPerson = Mapper.Map<PersonEditModel , Person>(person);
_personRepository.InsertOrUpdate(insertPerson);
_requirementRepository.Save();
return RedirectToAction(Actions.Index());
}
return EnrichedView(person);
}
This sort of ViewModel:
public class PersonEditModel
{
public string FirstName { get; set; }
public string LastName { get; set; }
public int Age { get; set; }
public int FavouriteTeam { get; set; }
public IEnumerable<SelectListItem> Teams {get;set;}
}
With this sort of Enricher:
public class PersonEditModelEnricher :
IModelEnricher<PersonEditModel>
{
private readonly ISelectListService _selectListService;
public PersonEditModelEnricher(ISelectListService selectListService)
{
_selectListService = selectListService;
}
public PersonEditModelEnrich(PersonEditModel model)
{
model.Teams = new SelectList(_selectListService.AllTeams(), "Value", "Text")
return model;
}
}
One other option is to decorate the ViewModel with attributes that define how the data is located to populate the select list. Like:
public class PersonEditModel
{
public string FirstName { get; set; }
public string LastName { get; set; }
public int Age { get; set; }
public int FavouriteTeam { get; set; }
[LoadSelectListData("Teams")]
public IEnumerable<SelectListItem> Teams {get;set;}
}
Now you can decorate an appropriate method in your select service with an attribute like:
[ProvideSelectData("Teams")]
public IEnumerable Teams()
{
return _teamRepository.All.ToSelectList(a => a.Name, a => a.TeamId);
}
Then for simple models with no complex enrichment just the generic enrichment process can handle it. If you want to do anything more complex you can define an enricher and it will be used if it exists.
Other options could be a convention over configuration approach where the Enricher looks at property name and type e.g. IEnumerable<SelectListItem> PossibleFirstDivisionTeams {get;set;} then matches this if it exists with a select list provider name in a class that say implements a marker interface ISelectListProvider. We went the attribute based one and just created Enums representing the various lists E.g. SelectList.AllFirstDivisionTeams. Could also try interfaces on ViewModel that just have a property collection for a selectlist. I don't really like interfaces on my ViewModels so we never did this
It all really depends on the scale of your application and how frequently same type of select list data is required across multiple models. Any specific questions or points you need clarified let me know
See this question. Also this blog post and this. Also this question on Automapper forum
The first question is if the options-list belongs to the ViewModel. A year or two ago I did the same, but what I see recently more and more as a "best practice" is that people add the list to the ViewBag/ViewData not to the ViewModel. That's an option and I tend to do the same for a one-shot drop-down list, but it doesn't answer the code-reuse question you are facing. For that I see two different approaches (and two more that I rule out).
Shared editor template. Create an editor template for the type that's represented by the dropdown. In this case - because we don't have the list of possible options in the ViewModel or the ViewBag - the template has to reach out for the options to the server. That's possible by adding an action method (that returns json) to a controller class. Either to a shared "LookupsController" (possibly an ApiController) or to the controller that the list-items' type belongs to.
Partial view. The drop down values belong to some type. The Controller of that type could have an action method that returns a partial view.
The benefit of the first one is that a nice #Html.EditorFor call will do the job. But I don't like the ajax dependency. Partly for that reason I would prefer the partial view.
And there is a third one: child action, but I don't see that a good pattern here. You can google what's the difference between child actions and partial views, for this case child action is the wrong choice. I also wouldn't recommend helper methods. I believe they are not designed for this use case either.
You could put that fetch in the default (null) constructor of MyVM if you don't need to vary its content.
Or you could use a PartialView that you render into the views that need t.
I like to use static classes often in a helper class that I can call from any view.
#Html.DropDownListFor(x => x.Field, PathToController.GetDropDown())
and then in your controller have a method built like this
public static List<SelectListItem> GetDropDown()
{
List<SelectListItem> ls = new List<SelectListItem>();
lm = (call database);
foreach (var temp in lm)
{
ls.Add(new SelectListItem() { Text = temp.name, Value = temp.id });
}
return ls;
}
Hopefully it helps.
If you really don't want to duplicate the code, place the code from the controllers into a helper class, and render the dropdown within a shared view (like _Layout.cshtml) that you'd then have to implement into your views by RenderPartial.
Create a partial view, _MyDropdownView.cstml, which uses the helper class you threw the code from the controllers in with something like the following:
#using MyNamespace.MyHelperClass
<div id="myDropdown">#Html.DropDownListFor(model => model.Prop, MyVM as SelectList, "--Select a Property--")</div>
Then, within your views:
#Html.RenderPartial("_MyDropdownView")
Extension methods to the rescue
public interface ISelectFoo {
IEnumerable<SelectListItem> FooDdl { get; set; }
}
public class FooModel:ISelectFoo { /* implementation */ }
public static void PopulateFoo(this ISelectFoo data, FooRepository repo)
{
data.FooDdl = repo.GetAll().ToSelectList(x => x.Id, x => x.Name);
}
//controller
var model=new ViewModel();
model.PopulateFoo(repo);
//a wild idea
public static T CreateModel<T>(this FooRepository repo) where T:ISelectFoo,new()
{
var model=new T();
model.FooDdl=repo.GetAll().ToSelectList(x => x.Id, x => x.Name);
return model;
}
//controller
var model=fooRepository.Create<MyFooModel>();
What about a Prepare method in a BaseController?
public class BaseController : Controller
{
/// <summary>
/// Prepares a new MyVM by filling the common properties.
/// </summary>
/// <returns>A MyVM.</returns>
protected MyVM PrepareViewModel()
{
return new MyVM()
{
FooDll = GetFooSelectList();
}
}
/// <summary>
/// Prepares the specified MyVM by filling the common properties.
/// </summary>
/// <param name="myVm">The MyVM.</param>
/// <returns>A MyVM.</returns>
protected MyVM PrepareViewModel(MyVM myVm)
{
myVm.FooDll = GetFooSelectList();
return myVm;
}
/// <summary>
/// Fetches the foos from the database and creates a SelectList.
/// </summary>
/// <returns>A collection of SelectListItems.</returns>
private IEnumerable<SelectListItem> GetFooSelectList()
{
return fooRepository.GetAll().ToSelectList(foo => foo.Id, foo => x.Name);
}
}
You can use this methods in the controller:
public class HomeController : BaseController
{
public ActionResult ActionX()
{
// Creates a new MyVM.
MyVM myVm = PrepareViewModel();
}
public ActionResult ActionY()
{
// Update an existing MyVM object.
var myVm = new MyVM
{
Property1 = "Value 1",
Property2 = DateTime.Now
};
PrepareViewModel(myVm);
}
}
Have an interface with all your properties that need to be automatically populated:
public interface ISelectFields
{
public IEnumerable<SelectListItem> FooDdl { get; set; }
}
Now all your view models that want to have those properties, implement that interface:
public class MyVM : ISelectFields
{
public IEnumerable<SelectListItem> FooDdl { get; set; }
}
Have a BaseController, override OnResultExecuting, find the ViewModel that is passed in and inject the properties to the interface:
public class BaseController : Controller
{
protected override void OnResultExecuting(ResultExecutingContext filterContext)
{
var viewResult = filterContext.Result as ViewResult;
if (viewResult != null)
{
var viewModel = viewResult.Model as ISelectFields;
if (viewModel != null)
{
viewModel.FooDdl = fooRepository.GetAll().ToSelectList(x => x.Id, x => x.Name)
}
}
base.OnResultExecuting(filterContext);
}
}
Now your controllers are very simple, everything is strongly typed, you are sticking with the DRY principle and you can just forget about populating that property, it will always be available in your views as long as your controllers inherit from the BaseController and your ViewModels implement the interface.
public class HomeController : BaseController
{
public ActionResult Index()
{
MyVM vm = new MyVM();
return View(vm); //you will have FooDdl available in your views
}
}
Why not use the advantages of RenderAction:
#(Html.RenderAction("ControllerForCommonElements", "CommonDdl"))
Create a controller, and an action that returns the Ddl and and just reference it in the views.
See some tips here on how you could use it
This way you can also cache this result. Actually the guys building StackOverflow talked about the pros of using this combined with different caching rules for different elements (i.e. if the ddl does not need to be 100% up to date you could cache it for a minute or so) in a podcast a while ago.

IValidatableObject only in some actions

I have a model that implement IValidatlableObject, and so custom error checking through Validate method.
When I create an object all is fine, but when I try to edit that object, I wan't to do that custom validation.
How can I know from wich action I'm calling the Validate method in order to no do the validation?
UPDATED:
This is mi model:
public class Ingredient : IValidatableObject
{
public int Id { get; set; }
[Required(ErrorMessage = "Required!!")]
public string Name { get; set; }
public virtual List<Product> Products { get; set; }
public Ingredient()
{
Products = new List<Product>();
}
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
using (var uow = new UnitOfWork())
{
var ingredient = uow.IngredientRepository.Get(i => i.Name ==Name).FirstOrDefault();
if (ingredient != null)
yield return new ValidationResult("Duplicate!!!.", new[] { "Name" });
}
}
}
}
So When I create an Ingredient I want to validate ALL (Attributes + IValidatable)
but when I edit an Ingrendient I only want to validate attributes (so I mean skip IValidatable)
Any method to know, inside the IValidatable method, from where I'm calling Validate ?
Thanks!!!
Check primary key of model - whether it is not null :)
The more "MVCish" correct way here is you actually have two classes, one for the Create method one for the edit. You can call off to a base class for any shared validation, anything then not shared wouldn't be checked here.
If you don't want to validate an object, don't call Model.IsValid (or Validate(), if you're doing it explicitly. Can't answer more than that without knowing more details about your problem.

Disable Required validation attribute under certain circumstances

I was wondering if it is possible to disable the Required validation attribute in certain controller actions. I am wondering this because on one of my edit forms I do not require the user to enter values for fields that they have already specified previously. However I then implement logic that when they enter a value it uses some special logic to update the model, such as hashing a value etc.
Any sugestions on how to get around this problem?
EDIT:
And yes client validation is a problem here to, as it will not allow them to submit the form without entering a value.
This problem can be easily solved by using view models. View models are classes that are specifically tailored to the needs of a given view. So for example in your case you could have the following view models:
public UpdateViewView
{
[Required]
public string Id { get; set; }
... some other properties
}
public class InsertViewModel
{
public string Id { get; set; }
... some other properties
}
which will be used in their corresponding controller actions:
[HttpPost]
public ActionResult Update(UpdateViewView model)
{
...
}
[HttpPost]
public ActionResult Insert(InsertViewModel model)
{
...
}
If you just want to disable validation for a single field in client side then you can override the validation attributes as follows:
#Html.TextBoxFor(model => model.SomeValue,
new Dictionary<string, object> { { "data-val", false }})
I know this question has been answered a long time ago and the accepted answer will actually do the work. But there's one thing that bothers me: having to copy 2 models only to disable a validation.
Here's my suggestion:
public class InsertModel
{
[Display(...)]
public virtual string ID { get; set; }
...Other properties
}
public class UpdateModel : InsertModel
{
[Required]
public override string ID
{
get { return base.ID; }
set { base.ID = value; }
}
}
This way, you don't have to bother with client/server side validations, the framework will behave the way it's supposed to. Also, if you define a [Display] attribute on the base class, you don't have to redefine it in your UpdateModel.
And you can still use these classes the same way:
[HttpPost]
public ActionResult Update(UpdateModel model)
{
...
}
[HttpPost]
public ActionResult Insert(InsertModel model)
{
...
}
You can remove all validation off a property with the following in your controller action.
ModelState.Remove<ViewModel>(x => x.SomeProperty);
#Ian's comment regarding MVC5
The following is still possible
ModelState.Remove("PropertyNameInModel");
Bit annoying that you lose the static typing with the updated API. You could achieve something similar to the old way by creating an instance of HTML helper and using NameExtensions Methods.
Client side
For disabling validation for a form, multiple options based on my research is given below. One of them would would hopefully work for you.
Option 1
I prefer this, and this works perfectly for me.
(function ($) {
$.fn.turnOffValidation = function (form) {
var settings = form.validate().settings;
for (var ruleIndex in settings.rules) {
delete settings.rules[ruleIndex];
}
};
})(jQuery);
and invoking it like
$('#btn').click(function () {
$(this).turnOffValidation(jQuery('#myForm'));
});
Option 2
$('your selector here').data('val', false);
$("form").removeData("validator");
$("form").removeData("unobtrusiveValidation");
$.validator.unobtrusive.parse("form");
Option 3
var settings = $.data($('#myForm').get(0), 'validator').settings;
settings.ignore = ".input";
Option 4
$("form").get(0).submit();
jQuery('#createForm').unbind('submit').submit();
Option 5
$('input selector').each(function () {
$(this).rules('remove');
});
Server Side
Create an attribute and mark your action method with that attribute. Customize this to adapt to your specific needs.
[AttributeUsage(AttributeTargets.All)]
public class IgnoreValidationAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
var modelState = filterContext.Controller.ViewData.ModelState;
foreach (var modelValue in modelState.Values)
{
modelValue.Errors.Clear();
}
}
}
A better approach has been described here Enable/Disable mvc server side validation dynamically
Personally I would tend to use the approach Darin Dimitrov showed in his solution.
This frees you up to be able to use the data annotation approach with validation AND have separate data attributes on each ViewModel corresponding to the task at hand.
To minimize the amount of work for copying between model and viewmodel you should look at AutoMapper or ValueInjecter. Both have their individual strong points, so check them both.
Another possible approach for you would be to derive your viewmodel or model from IValidatableObject. This gives you the option to implement a function Validate.
In validate you can return either a List of ValidationResult elements or issue a yield return for each problem you detect in validation.
The ValidationResult consists of an error message and a list of strings with the fieldnames. The error messages will be shown at a location near the input field(s).
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if( NumberField < 0 )
{
yield return new ValidationResult(
"Don't input a negative number",
new[] { "NumberField" } );
}
if( NumberField > 100 )
{
yield return new ValidationResult(
"Don't input a number > 100",
new[] { "NumberField" } );
}
yield break;
}
The cleanest way here I believe is going to disable your client side validation and on the server side you will need to:
ModelState["SomeField"].Errors.Clear (in your controller or create an action filter to remove errors before the controller code is executed)
Add ModelState.AddModelError from your controller code when you detect a violation of your detected issues.
Seems even a custom view model here wont solve the problem because the number of those 'pre answered' fields could vary. If they dont then a custom view model may indeed be the easiest way, but using the above technique you can get around your validations issues.
this was someone else's answer in the comments...but it should be a real answer:
$("#SomeValue").removeAttr("data-val-required")
tested on MVC 6 with a field having the [Required] attribute
answer stolen from https://stackoverflow.com/users/73382/rob above
I was having this problem when I creating a Edit View for my Model and I want to update just one field.
My solution for a simplest way is put the two field using :
<%: Html.HiddenFor(model => model.ID) %>
<%: Html.HiddenFor(model => model.Name)%>
<%: Html.HiddenFor(model => model.Content)%>
<%: Html.TextAreaFor(model => model.Comments)%>
Comments is the field that I only update in Edit View, that not have Required Attribute.
ASP.NET MVC 3 Entity
AFAIK you can not remove attribute at runtime, but only change their values (ie: readonly true/false) look here for something similar .
As another way of doing what you want without messing with attributes I will go with a ViewModel for your specific action so you can insert all the logic without breaking the logic needed by other controllers.
If you try to obtain some sort of wizard (a multi steps form) you can instead serialize the already compiled fields and with TempData bring them along your steps. (for help in serialize deserialize you can use MVC futures)
What #Darin said is what I would recommend as well. However I would add to it (and in response to one of the comments) that you can in fact also use this method for primitive types like bit, bool, even structures like Guid by simply making them nullable. Once you do this, the Required attribute functions as expected.
public UpdateViewView
{
[Required]
public Guid? Id { get; set; }
[Required]
public string Name { get; set; }
[Required]
public int? Age { get; set; }
[Required]
public bool? IsApproved { get; set; }
//... some other properties
}
As of MVC 5 this can be easily achieved by adding this in your global.asax.
DataAnnotationsModelValidatorProvider.AddImplicitRequiredAttributeForValueTypes = false;
I was looking for a solution where I can use the same model for an insert and update in web api. In my situation is this always a body content. The [Requiered] attributes must be skipped if it is an update method.
In my solution, you place an attribute [IgnoreRequiredValidations] above the method. This is as follows:
public class WebServiceController : ApiController
{
[HttpPost]
public IHttpActionResult Insert(SameModel model)
{
...
}
[HttpPut]
[IgnoreRequiredValidations]
public IHttpActionResult Update(SameModel model)
{
...
}
...
What else needs to be done?
An own BodyModelValidator must becreated and added at the startup.
This is in the HttpConfiguration and looks like this: config.Services.Replace(typeof(IBodyModelValidator), new IgnoreRequiredOrDefaultBodyModelValidator());
using Owin;
using your_namespace.Web.Http.Validation;
[assembly: OwinStartup(typeof(your_namespace.Startup))]
namespace your_namespace
{
public class Startup
{
public void Configuration(IAppBuilder app)
{
Configuration(app, new HttpConfiguration());
}
public void Configuration(IAppBuilder app, HttpConfiguration config)
{
config.Services.Replace(typeof(IBodyModelValidator), new IgnoreRequiredOrDefaultBodyModelValidator());
}
...
My own BodyModelValidator is derived from the DefaultBodyModelValidator. And i figure out that i had to override the 'ShallowValidate' methode. In this override i filter the requierd model validators.
And now the IgnoreRequiredOrDefaultBodyModelValidator class and the IgnoreRequiredValidations attributte class:
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Web.Http.Controllers;
using System.Web.Http.Metadata;
using System.Web.Http.Validation;
namespace your_namespace.Web.Http.Validation
{
public class IgnoreRequiredOrDefaultBodyModelValidator : DefaultBodyModelValidator
{
private static ConcurrentDictionary<HttpActionBinding, bool> _ignoreRequiredValidationByActionBindingCache;
static IgnoreRequiredOrDefaultBodyModelValidator()
{
_ignoreRequiredValidationByActionBindingCache = new ConcurrentDictionary<HttpActionBinding, bool>();
}
protected override bool ShallowValidate(ModelMetadata metadata, BodyModelValidatorContext validationContext, object container, IEnumerable<ModelValidator> validators)
{
var actionContext = validationContext.ActionContext;
if (RequiredValidationsIsIgnored(actionContext.ActionDescriptor.ActionBinding))
validators = validators.Where(v => !v.IsRequired);
return base.ShallowValidate(metadata, validationContext, container, validators);
}
#region RequiredValidationsIsIgnored
private bool RequiredValidationsIsIgnored(HttpActionBinding actionBinding)
{
bool ignore;
if (!_ignoreRequiredValidationByActionBindingCache.TryGetValue(actionBinding, out ignore))
_ignoreRequiredValidationByActionBindingCache.TryAdd(actionBinding, ignore = RequiredValidationsIsIgnored(actionBinding.ActionDescriptor as ReflectedHttpActionDescriptor));
return ignore;
}
private bool RequiredValidationsIsIgnored(ReflectedHttpActionDescriptor actionDescriptor)
{
if (actionDescriptor == null)
return false;
return actionDescriptor.MethodInfo.GetCustomAttribute<IgnoreRequiredValidationsAttribute>(false) != null;
}
#endregion
}
[AttributeUsage(AttributeTargets.Method, Inherited = true)]
public class IgnoreRequiredValidationsAttribute : Attribute
{
}
}
Sources:
Using string debug = new StackTrace().ToString() to find out who is
handeling the model validation.
https://learn.microsoft.com/en-us/aspnet/web-api/overview/advanced/configuring-aspnet-web-api to know how set my own validator.
https://github.com/ASP-NET-MVC/aspnetwebstack/blob/master/src/System.Web.Http/Validation/DefaultBodyModelValidator.cs to figure out what this validator is doing.
https://github.com/Microsoft/referencesource/blob/master/System.Web/ModelBinding/DataAnnotationsModelValidator.cs to figure out why the IsRequired property is set on true. Here you can also find the original Attribute as a property.
If you don't want to use another ViewModel you can disable client validations on the view and also remove the validations on the server for those properties you want to ignore. Please check this answer for a deeper explanation https://stackoverflow.com/a/15248790/1128216
In my case the same Model was used in many pages for re-usability purposes. So what i did was i have created a custom attribute which checks for exclusions
public class ValidateAttribute : ActionFilterAttribute
{
public string Exclude { get; set; }
public string Base { get; set; }
public override void OnActionExecuting(HttpActionContext actionContext)
{
if (!string.IsNullOrWhiteSpace(this.Exclude))
{
string[] excludes = this.Exclude.Split(',');
foreach (var exclude in excludes)
{
actionContext.ModelState.Remove(Base + "." + exclude);
}
}
if (actionContext.ModelState.IsValid == false)
{
var mediaType = new MediaTypeHeaderValue("application/json");
var error = actionContext.ModelState;
actionContext.Response = actionContext.Request.CreateResponse(HttpStatusCode.OK, error.Keys, mediaType);
}
}
}
and in your controller
[Validate(Base= "person",Exclude ="Age,Name")]
public async Task<IHttpActionResult> Save(User person)
{
//do something
}
Say the Model is
public class User
{
public int Id { get; set; }
[Required]
public string Name { get; set; }
[Range(18,99)]
public string Age { get; set; }
[MaxLength(250)]
public string Address { get; set; }
}
This one worked for me:
$('#fieldId').rules('remove', 'required');
Yes it is possible to disable Required Attribute. Create your own custom class attribute (sample code called ChangeableRequired) to extent from RequiredAtribute and add a Disabled Property and override the IsValid method to check if it is disbaled. Use reflection to set the disabled poperty, like so:
Custom Attribute:
namespace System.ComponentModel.DataAnnotations
{
public class ChangeableRequired : RequiredAttribute
{
public bool Disabled { get; set; }
public override bool IsValid(object value)
{
if (Disabled)
{
return true;
}
return base.IsValid(value);
}
}
}
Update you property to use your new custom Attribute:
class Forex
{
....
[ChangeableRequired]
public decimal? ExchangeRate {get;set;}
....
}
where you need to disable the property use reflection to set it:
Forex forex = new Forex();
// Get Property Descriptor from instance with the Property name
PropertyDescriptor descriptor = TypeDescriptor.GetProperties(forex.GetType())["ExchangeRate"];
//Search for Attribute
ChangeableRequired attrib = (ChangeableRequired)descriptor.Attributes[typeof(ChangeableRequired)];
// Set Attribute to true to Disable
attrib.Disabled = true;
This feels nice and clean?
NB: The validation above will be disabled while your object instance is alive\active...

Passing DataContext between windows in MVVM

On the main window onClick I have
AddNoticeAboutWrongCity addNoticeAboutWrongCity = new AddNoticeAboutWrongCity();
addNoticeAboutWrongCity.DataContext = ((VerificationViewModule)this.DataContext).WrongCityNotice;
addNoticeAboutWrongCity.ShowDialog();
At popup window there a lot of textboxes and two buttons
Delete object:
this.DataContext = null;
And second option "Save edited notice" which is not usable , because every change of user affection datacontext on main window,and this is demand from design department :)
I don't know why first option(it's "implementation" doesn't work.
Second explanation:
On the ParentWindow I have list of Notices and I can click EditSelectedNotice.
On the EditNoticeWindow I can edit Notice or delete Notice.
Editinig works(After closing EditNoticeWindow I see changed notice on the ParentWindow), but deleting doesn't (Notice is still in collection - on control and in this.DataContext)
My ViewModel:
class VerificationViewModule
{
public ObservableCollection<ReporterNotice> ReporterNotices { get; set; }
public ReporterNotice OtherNotice
{
get
{
return ReporterNotices.Where(n => n.Type == ReporterNoticeType.Other).FirstOrDefault();
}
}
public ReporterNotice DuplicateNotice
{
get
{
return ReporterNotices.Where(n => n.Type == ReporterNoticeType.Duplicate).FirstOrDefault();
}
}
public ReporterNotice WrongCityNotice
{
get
{
return ReporterNotices.Where(n => n.Type == ReporterNoticeType.WrongCity).FirstOrDefault();
}
set { if(value==null)
{
ReporterNotices.Remove(ReporterNotices.Where(n => n.Type == ReporterNoticeType.WrongCity).First());
}
else
{
if (ReporterNotices.Where(n => n.Type == ReporterNoticeType.WrongCity).FirstOrDefault()==null)//there is always only max one instance of this type of notice
{
ReporterNotices.Add(value);
}
else
{
var c = ReporterNotices.Where(n => n.Type == ReporterNoticeType.WrongCity).First();
c = value;
}
}}
}
public VerificationViewModule()
{
ObservableCollection<ReporterNotice> loadedReporterNotices = new ObservableCollection<ReporterNotice>();
loadedReporterNotices.Add(new ReporterNotice() { Content = "Dublic", Type = ReporterNoticeType.WrongCity });
loadedReporterNotices.Add(new ReporterNotice() { Content = "Hilton", Type = ReporterNoticeType.Duplicate });
loadedReporterNotices.Add(new ReporterNotice() { Content = "Another notice", Type = ReporterNoticeType.Other });
ReporterNotices = loadedReporterNotices;
}
}
You can try the following. Implement the mediator to display windows and make sure that you use view models for the DataContext for both the main and edit windows. It is important to tell the main view model that the object is being deleted. This is done via a callback and routing that through a command on the EditNoticeViewModel
//This viewmodel is on the main windows datacontext
public class ParentViewModel
{
private readonly IWindowMediator _mediator;
public ParentViewModel(IWindowMediator mediator)
{
_mediator = mediator;
}
public ObservableCollection<Notice> Notices { get; private set; } //bound to list in xaml
public void OpenNotice(Notice notice)
{
//open the window using the Mediator pattern rather than a new window directly
_mediator.Open(new EditNoticeViewModel(notice, DeleteNotice));
}
private void DeleteNotice(Notice notice)
{
//This will remove it from the main window list
Notices.Remove(notice);
}
}
//view model for EditNoticeWindow
public class EditNoticeViewModel
{
public EditNoticeViewModel(Action<Notice> deleteCallback, Notice notice)
{
Model = notice;
DeleteCommand = new DelegateCommand((a) => deleteCallback(Model));
}
//Bind in xaml to the Command of a button
DelegateCommand DeleteCommand { get; private set; }
//bound to the controls in the xaml.
public Notice Model { get; private set; }
}
//This is a basic interface, you can elaborate as needed
//but it handles the opening of windows. Attach the view model
//to the data context of the window.
public interface IWindowMediator
{
void Open<T>(T viewModel);
}
Depending on implementation you might want to close the view when the delete button gets pushed. You can do this by implementing something like the as described here with respect to WorkspaceViewModel
Why don't you wrap the WrongCityNotice in a viewModel implementing IReporterNotice and having a reference to the parent viewmodel and a Delete method:
public void Delete() { _parentvm.Delete(_wrongCityNotice); }
You can use this wrapper as DataContext.
You're trying to destroy the DataContext. C# doesn't work that way. Setting an object reference to null doesn't delete the object, it only removes the reference to it. (When nothing references an object anymore it gets garbage collected, but you can't destroy an object directly).
DataContext = null only means that locally your DataContext doesn't point to any object any more. The main view model still has a reference however so nothing changes there. You'll have to ask the main view model to remove the notification from it's collection (probably through a callback method (Action) is best so you don't have to know about the parent view model).

Categories