I have created a custom Select Component which inherits from InputBase. Everything works expect for validation. I'm not sure how to ensure the "invalid" class is added to my custom form compoenent below as it does with other Blazor form compoenent such as InputText:
Select.razor
<div id="select-#Id" class="component-select" #ref="ReferenceToDiv">
#foreach (var option in Options)
{
<div #onclick="() => OnSelect(option)" class="option">#option.Text</div>
}
</div>
Select.razor.cs
namespace Accounting.Web.Components.Forms
{
public partial class SelectOption
{
public string Text { get; set; } = default!;
public string Value { get; set; } = default!;
}
public partial class Select<TValue> : InputBase<TValue>
{
[Parameter]
public ElementReference ReferenceToDiv { get; set; }
[Parameter]
public string Id { get; set; } = String.Empty;
[Parameter]
public List<SelectOption> Options { get; set; }
[Parameter]
public string PlaceholderText { get; set; } = "Select...";
[Parameter]
public EventCallback<Select<TValue>> OnSelected { get; set; } = default!;
public SelectOption SelectedOption { get; set; } = default!;
protected override bool TryParseValueFromString(string? value, [MaybeNullWhen(false)] out TValue result, [NotNullWhen(false)] out string? validationErrorMessage)
{
if (BindConverter.TryConvertTo<TValue>(value, null, out result))
{
validationErrorMessage = null;
return false;
}
else
{
validationErrorMessage = "Err : Select value";
return false;
}
}
public void OnSelect(SelectOption? option)
{
TValue tmpValue;
option.Value = ""; // this line is to test the validation, it should always fail and add 'invalid' clas to the component classes
BindConverter.TryConvertTo<TValue>(option.Value, null, out tmpValue);
CurrentValue = tmpValue;
SelectedOption = option;
}
}
}
Index.razor
<EditForm Model="#AddDto" class="card card-body bg-light mt-5" OnValidSubmit="OnValidSubmit">
<DataAnnotationsValidator />
<Accounting.Web.Components.Forms.Select
Id="InvestmentEntitySelect"
Options="#entityOptions" #bind-Value="AddDto.InvestmentEntityId">
</Accounting.Web.Components.Forms.Select>
<button type="submit" class="btn btn-primary full-width">Add</button>
</EditForm>
Try adding #CssClass to the class - <div id="select-#Id" class="component-select #CssClass" #ref="ReferenceToDiv">.
The way it works in ComponentBase is:
protected string CssClass
{
get
{
var fieldClass = EditContext?.FieldCssClass(FieldIdentifier);
return AttributeUtilities.CombineClassNames(AdditionalAttributes, fieldClass) ?? string.Empty;
}
}
where EditContext?.FieldCssClass(FieldIdentifier) is an extension method that returns the valid/invalid css based on whether there's a validation message for the field defined by FieldIdentifier. The method combines this css string with the css supplied in the component attributes class.
This is used in an implementation (this is InputSelect) like this:
protected override void BuildRenderTree(RenderTreeBuilder builder)
{
builder.OpenElement(0, "select");
builder.AddMultipleAttributes(1, AdditionalAttributes);
builder.AddAttributeIfNotNullOrEmpty(2, "class", CssClass);
builder.AddAttribute(3, "multiple", _isMultipleSelect);
//...
For the record the actual GetFieldCssClass code is:
public virtual string GetFieldCssClass(EditContext editContext, in FieldIdentifier fieldIdentifier)
{
var isValid = !editContext.GetValidationMessages(fieldIdentifier).Any();
if (editContext.IsModified(fieldIdentifier))
{
return isValid ? "modified valid" : "modified invalid";
}
else
{
return isValid ? "valid" : "invalid";
}
}
Related
Need a little help creating custom select component
I am attempting to create a custom form select component. The component will contain my own custom markup rather than using the tag as it needs a completely different UI beyond which I can style with css.
The component should be able to bind it's value to a string / int / decimal model property which is where I am having trouble.
This is what I have so far:
MySelect.razor
#typeparam TValue
#inherits InputBase<TValue>
#namespace Accounting.Web.Components
#foreach (var option in Options)
{
<button #onclick="OnClick(option.Value)">#option.Value</button>
}
MySelect.razor.cs
namespace Accounting.Web.Components
{
public partial class MySelectOption<TValue>
{
public int Id { get; set; }
public TValue Value { get; set; }
}
public partial class MySelect<TValue> : InputBase<TValue>
{
[Parameter]
public string Id { get; set; } = "ESelect";
[Parameter]
public List<MySelectOption<TValue>> Options { get; set; }
protected override bool TryParseValueFromString(string? value, [MaybeNullWhen(false)] out TValue result, [NotNullWhen(false)] out string? validationErrorMessage)
{
throw new NotImplementedException();
}
public void OnClick(TValue value)
{
Value = value;
}
}
}
And then in Index.razor:
<MySelect Options="#options" #bind-Value="AddDto.Description" TValue="string">
So when using the component I should be able to bind it to a property of any type (but usually int or string) which I pass as the type param TValue.
However, the line below is causing an issue:
<button #onclick="OnClick(option.Value)">#option.Value</button>
Argument 2: cannot convert from 'void' to 'Microsoft.AspNetCore.Components.EventCallback'
How can I pass the option.Value (which is always a string) to the onCLick event? Or alternatively modify the code above so that I can accomplish my initially stated goal?
You have more that one issue, but the important one is trying to update Value. Value is an "input" into the control. The updated value is passed back to parent by calling ValueChanged. However, calling ValueChanged directly bypasses the built in functionality in InputBase and it's interaction with the EditContext.
This demonstrates the basics of inheriting from InputBase.
To leverage the built in functionality, you need to either:
Set the value by setting CurrentValueAsString from the markup and then providing a custom TryParseValueFromString to convert from a string to your type (there's a BindConverter helper you can use - it's what InputNumber and other input controls use).
Set the value directly by setting CurrentValue. This bypasses TryParseValueFromString.
Your MySelect.
I've prettied up your buttons and abstracted your list to an IEnumerable.
#typeparam TValue
#inherits InputBase<TValue>
#using Microsoft.AspNetCore.Components.Forms;
#using Microsoft.AspNetCore.Components;
#using System.Diagnostics.CodeAnalysis;
<div class="btn-group" role="group">
#foreach (var option in Options)
{
<button class="#btnColour(option.Value)" #onclick="() => OnClick(option.Value)">#option.Value</button>
}
</div>
#code {
[Parameter] public IEnumerable<MySelectOption<TValue>> Options { get; set; } = new List<MySelectOption<TValue>>();
private string btnColour(TValue? value)
{
if (this.Value is null)
return "btn btn-outline-primary";
return this.Value.Equals(value)
? "btn btn-primary"
: "btn btn-outline-primary";
}
protected override bool TryParseValueFromString(string? value, [MaybeNullWhen(false)] out TValue result, [NotNullWhen(false)] out string? validationErrorMessage)
{
throw new NotImplementedException();
}
public void OnClick(TValue? value)
{
CurrentValue = value;
}
}
And then here's a demo page to show it in use.
#page "/"
<PageTitle>Index</PageTitle>
<h1>Hello, world!</h1>
<EditForm Model=#model>
<MySelect Options="#options" #bind-Value=model.Description TValue="string" />
</EditForm>
<div class="alert alert-info m-3">
Description: #model.Description
</div>
#code {
private Model model = new();
IEnumerable<MySelectOption<string>> options =
new List<MySelectOption<string>>() {
new MySelectOption<string> { Id = 1, Value = "France" },
new MySelectOption<string> { Id = 1, Value = "Spain" },
new MySelectOption<string> { Id = 1, Value = "Portugal" },
};
public class Model
{
public string? Description { get; set; }
}
}
For reference you can find the source code for all the standard InputBase controls here: https://github.com/dotnet/aspnetcore/tree/main/src/Components/Web/src/Forms
With help and suggestions from previous answers, below is the solution I arrived at:
Index.razor
<MySelect Options="#options" #bind-Value="AddDto.InvestmentEntityId">
</MySelect>
#AddDto.InvestmentEntityId // integer property
<MySelect Options="#options" #bind-Value="AddDto.Description">
</MySelect>
#AddDto.Description // string property
MySelect.razor
#typeparam TValue
#inherits InputBase<TValue>
#namespace Accounting.Web.Components
#foreach (var option in Options)
{
<button #onclick="() => OnClick(option.Value)">#option.Value</button>
}
MySelect.razor.cs
namespace Accounting.Web.Components
{
public partial class MySelectOption
{
public int Id { get; set; }
public string Value { get; set; }
}
public partial class MySelect<TValue> : InputBase<TValue>
{
[Parameter]
public List<MySelectOption> Options { get; set; }
protected override bool TryParseValueFromString(string? value, [MaybeNullWhen(false)] out TValue result, [NotNullWhen(false)] out string? validationErrorMessage)
{
if (BindConverter.TryConvertTo<TValue>(value, null, out result))
{
validationErrorMessage = null;
}
else
{
validationErrorMessage = "Err : Select value";
}
}
public void OnClick(string value)
{
TValue tmpValue;
BindConverter.TryConvertTo<TValue>(value, null, out tmpValue);
CurrentValue = tmpValue;
}
}
}
It's probably not perfect but I hope it helps anyone looking to do the same.
I am trying to set a gridview column as a link column but when passing values from view model they do not display. I am not sure where I went wrong. Please help me with this issue. Thanks!
ViewModel
public class ViewModelBase
{
//Current user data
public string Id { get; set; }
public string UserName { get; set; }
public string Name { get; set; }
//Report data
public string ReportName { get; set; }
public string Brand { get; set; }
public string FileName { get; set; }
public string FilePath { get; set; }
public virtual List<Report> Reports { get; set; }
}
public class ReportViewModel : ViewModelBase
{
}
public class CurrentUserViewModel : ViewModelBase
{
}
User Reports:
public class ReportRepository
{
public List<Report> CurrentUserReports()
{
FulfillmentContext db = new FulfillmentContext();
var userRepository = new UserRepository();
var currentUser = userRepository.GetCurrentUser();
var allReports = db.Reports;
return (from r in allReports.ToList()
where (r.UserId == currentUser.Id)
orderby r.CreatedOn descending
select r).ToList();
}
}
Controller:
public ActionResult Index()
{
var reportRepository = new ReportRepository();
var userRepository = new UserRepository();
var userReports = reportRepository.CurrentUserReports();
var currentUser = userRepository.GetCurrentUser();
var viewModel = new ViewModelBase
{
Reports = userReports,
Name = currentUser.Name,
UserName = currentUser.UserName
};
return View(viewModel);
}
View:
#model FulfillmentPortal.ViewModels.ViewModelBase
#{
ViewBag.Title = "Home Page";
Layout = "~/Views/Shared/_Layout.cshtml";
var grid = new WebGrid(Model.Reports, rowsPerPage: 20, canPage: true, canSort: true);
}
<script>pageindicator = 0;</script>
<div>
<div class="sc-home-splash"></div>
<div class="panel panel-primary">
<div class="panel-heading">REPORTS</div>
<div class="panel-body" style="padding-left: 35px;">
#if (Model.Reports.Count() != 0)
{
<div id="grid">
#grid.GetHtml(tableStyle: "grid-table",
headerStyle: "grid-header",
footerStyle: "grid-footer",
alternatingRowStyle: "grid-alter-row",
rowStyle: "grid-row",
mode: WebGridPagerModes.All,
firstText: "<< First",
previousText: "< Prev",
nextText: "Next >",
lastText: "Last >>",
columns: grid.Columns(
grid.Column("ReportName", "Report Name", format: ##Model.ReportName),
grid.Column("Brand", "Brand"),
grid.Column("FileName", "File Name"),
grid.Column("CreatedOn", "Date")
))
</div>
}
else
{
<p>No Records Found!</p>
}
</div>
</div>
At this point where I am using the format: #Model.FileName and #Model.ReportName the Model values are empty. Please help me find whats wrong.
You forgot tot set those two properties of the ViewModel in your Index function. You only set Reports, Name and User name.
Set those two
I'm developing an ASP.NET MVC 5 application, with C# and .NET Framework 4.6.1.
I have this View:
#model MyProject.Web.API.Models.AggregationLevelConfViewModel
[...]
#Html.DropDownListFor(m => m.Configurations[0].HelperCodeType, (SelectList)Model.HelperCodeTypeItems, new { id = "Configurations[0].HelperCodeType" })
The ViewModel is:
public class AggregationLevelConfViewModel
{
private readonly List<GenericIdNameType> codeTypes;
private readonly List<GenericIdNameType> helperCodeTypes;
public IEnumerable<SelectListItem> CodeTypeItems
{
get { return new SelectList(codeTypes, "Id", "Name"); }
}
public IEnumerable<SelectListItem> HelperCodeTypeItems
{
get { return new SelectList(helperCodeTypes, "Id", "Name"); }
}
public int ProductionOrderId { get; set; }
public string ProductionOrderName { get; set; }
public IList<Models.AggregationLevelConfiguration> Configurations { get; set; }
public AggregationLevelConfViewModel()
{
// Load CodeTypes to show it as a DropDownList
byte[] values = (byte[])Enum.GetValues(typeof(CodeTypes));
codeTypes = new List<GenericIdNameType>();
helperCodeTypes = new List<GenericIdNameType>();
for (int i = 0; i < values.Length; i++)
{
GenericIdNameType cType = new GenericIdNameType()
{
Id = values[i].ToString(),
Name = EnumHelper.GetDescription((CodeTypes)values[i])
};
if (((CodeTypes)values[i]) != CodeTypes.NotUsed)
codeTypes.Add(cType);
helperCodeTypes.Add(cType);
}
}
}
And Models.AggregationLevelConfiguration is:
public class AggregationLevelConfiguration
{
public byte AggregationLevelConfigurationId { get; set; }
public int ProductionOrderId { get; set; }
public string Name { get; set; }
public byte CodeType { get; set; }
public byte HelperCodeType { get; set; }
public int PkgRatio { get; set; }
public int RemainingCodes { get; set; }
}
I need to set selected value in these properties:
public IEnumerable<SelectListItem> CodeTypeItems
{
get { return new SelectList(codeTypes, "Id", "Name"); }
}
public IEnumerable<SelectListItem> HelperCodeTypeItems
{
get { return new SelectList(helperCodeTypes, "Id", "Name"); }
}
But I can't set it in new SelectList(codeTypes, "Id", "Name"); or new SelectList(helperCodeTypes, "Id", "Name"); because the selected value are in Configurations array: fields AggregationLevelConfiguration.CodeType and AggregationLevelConfiguration.HelperCodeType.
I think I have to set selected value in the View, but I don't know how to do it.
How can I set the selected values?
Unfortunately #Html.DropDownListFor() behaves a little differently than other helpers when rendering controls in a loop. This has been previously reported as an issue on CodePlex (not sure if its a bug or just a limitation)
The are 2 option to solve this to ensure the correct option is selected based on the model property
Option 1 (using an EditorTemplate)
Create a custom EditorTemplate for the type in the collection. Create a partial in /Views/Shared/EditorTemplates/AggregationLevelConfiguration.cshtml (note the name must match the name of the type
#model yourAssembly.AggregationLevelConfiguration
#Html.DropDownListFor(m => m.HelperCodeType, (SelectList)ViewData["CodeTypeItems"])
.... // other properties of AggregationLevelConfiguration
and then in the main view, pass the SelectList to the EditorTemplate as additionalViewData
#using (Html.BeginForm())
{
...
#Html.EditorFor(m => m.Configurations , new { CodeTypeItems = Model.CodeTypeItems })
...
Option 2 (generate a new SelectList in each iteration and set the selectedValue)
In this option your property CodeTypeItems should to be IEnumerable<GenericIdNameType>, not a SelectList (or just make codeTypes a public property). Then in the main view
#Html.DropDownListFor(m => m.Configurations[0].HelperCodeType, new SelectList(Model.CodeTypeItems, "Id", "Name", Model.Configurations[0].HelperCodeType)
Side note: there is no need to use new { id = "Configurations[0].HelperCodeType" - the DropDownListFor() method already generated that id attribute
I wrote this class to overcome an issue I was having with selecting an option in an html select list. I hope it helps someone.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web;
namespace Login_page.Models
{
public class HTMLSelect
{
public string id { get; set; }
public IEnumerable<string> #class { get; set; }
public string name { get; set; }
public Boolean required { get; set; }
public string size { get; set; }
public IEnumerable<SelectOption> SelectOptions { get; set; }
public HTMLSelect(IEnumerable<SelectOption> options)
{
}
public HTMLSelect(string id, string name)
{
this.id = id;
this.name = name;
}
public HTMLSelect(string id, string name, bool required, IEnumerable<SelectOption> options)
{
this.id = id;
this.name = name;
this.required = required;
}
private string BuildOpeningTag()
{
StringBuilder text = new StringBuilder();
text.Append("<select");
text.Append(this.id != null ? " id=" + '"' + this.id + '"' : "");
text.Append(this.name != null ? " name=" + '"' + this.name + '"' : "");
text.Append(">");
return text.ToString();
}
public string GenerateSelect(IEnumerable<SelectOption> options)
{
StringBuilder selectElement = new StringBuilder();
selectElement.Append(this.BuildOpeningTag());
foreach (SelectOption option in options)
{
StringBuilder text = new StringBuilder();
text.Append("\t");
text.Append("<option value=" + '"' + option.Value + '"');
text.Append(option.Selected != false ? " selected=" + '"' + "selected" + '"' + ">" : ">");
text.Append(option.Text);
text.Append("</option>");
selectElement.Append(text.ToString());
}
selectElement.Append("</select");
return selectElement.ToString();
}
}
public class SelectOption
{
public string Text { get; set; }
public Boolean Selected { get; set; }
public string Value { get; set; }
}
}
And
public IEnumerable<SelectOption> getOrderTypes()
{
List<SelectOption> orderTypes = new List<SelectOption>();
if (this.orderType == "OptionText")
{
orderTypes.Add(new SelectOption() { Value = "1", Text = "OptionText", Selected = true });
} else
{
orderTypes.Add(new SelectOption() { Value = "2", Text = "OptionText2" });
}
}
And to use it:
#{
Login_page.Models.HTMLSelect selectElement = new Login_page.Models.HTMLSelect("order-types", "order-types");
}
#Html.Raw(selectElement.GenerateSelect(Model.getOrderTypes()));
I leave this in case it helps someone else. I had a very similar problem and none of the answers helped.
We had in a view this line at the top:
IEnumerable<SelectListItem> exitFromTrustDeed = (ViewData["ExitFromTrustDeed"] as IEnumerable<string>).Select(e => new SelectListItem() {
Value = e,
Text = e,
Selected = Model.ExitFromTrustDeed == e
});
and then below in the view:
#Html.DropDownListFor(m => m.ExitFromTrustDeed, exitFromTrustDeed, new { #class = "form-control" })
We had a property in my ViewData with the same name as the selector for the lambda expression and for some reason that makes the dropdown to be rendered without any option selected.
We changed the name in ViewData to ViewData["ExitFromTrustDeed2"] and that made it work as expected.
Weird though.
I developing the MVC application.
I am stuck in LINQ Syntax.
I wan to show the sum of List Items in index view of parent.
Please check code below.
In Model I have two classes.
public class StockAdjustment
{
public int Id { get; set; }
public List<StockAdjustmentItem> StockAdjustmentItems { get; set; }
public int SumOfStockAdjustmentItemQuantity
{
get
{
if (this.StockAdjustmentItems != null)
{
return this.StockAdjustmentItems.Sum(s=>s.OriginalQuantity);
}
return 0;
}
}
}
public class StockAdjustmentItem
{
public int Id { get; set; }
public int OriginalQuantity { get; set; }
public StockAdjustment StockAdjustment { get; set; }
}
public StockAdjustment GetAll(int Id)
{
oStockAdjustment = GetStockAdjustmentById(Id);
var prepo = new ProductRepo();
oStockAdjustment.StockAdjustmentItems = new List<StockAdjustmentItem>();
StockAdjustmentItem ai1 = new StockAdjustmentItem();
ai1.Id = 1 ;
ai1.OriginalQuantity = 250;
oStockAdjustment.StockAdjustmentItems.Add(ai1);
StockAdjustmentItem ai2 = new StockAdjustmentItem();
ai2.Id = 1;
ai2.OriginalQuantity = 375;
oStockAdjustment.StockAdjustmentItems.Add(ai2);
return oStockAdjustment;
}
Now I have controller Code
public ActionResult Index(string searchContent = "")
{
AdjustmentRepo oAdjustmentRepo = new AdjustmentRepo();
var adjustments = from adjustment in oAdjustmentRepo.GetAll() select adjustment;
ViewBag.StockAdjustmentList = adjustments;
return View(adjustments);
}
This Working perfectly fine...
Now, the problem comes when, I am trying to show StockAdjustment in List.
I have to show the sum of the OriginalQuantites of StockAdjustmentItems in the Front of StockAdjustment item in grid.
in above Exmaple I want to show 650(250 + 375) in the row of a gird.
#model IEnumerable<StockWatchServices.DomainClass.StockAdjustment>
#Html.Grid(Model).Columns(columns =>
{
columns.Add(c=>c.StockAdjustmentItems.Sum( OriginalQuantity ???? Im stuck here... )
}
What should I write here ?
I can see like this...
Create a getter only property on the StockAdjustment class
public class StockAdjustment
{
public int Id { get; set; }
public List<StockAdjustmentItem> StockAdjustmentItems { get; set; }
public int SumOfStockAdjustmentItemQuantity
{
get
{
if (this.StockAdjustmentItems != null)
{
return this.StockAdjustmentItems.Sum(s=>s.OriginalQuantity);
}
return 0;
}
}
}
And then in your Razor view:
#Html.Grid(Model).Columns(columns =>
{
columns.Add(c => c.SumOfStockAdjustmentItemQuantity)
}
Can you try with below code :
#Html.Grid(Model).Columns(columns =>
{
columns.Add(c => c.StockAdjustmentItems.Where(quantity => quantity.OriginalQuantity != null).Sum(sum => sum.OriginalQuantity).ToString());
})
I have a model with properties that look like this:
public class YourDetails {
[Required(ErrorMessage = "Code is required")]
[StringLength(10, ErrorMessage = "Code length is wrong", MinimumLength = 2)]
[Range(0, int.MaxValue, ErrorMessage = "Please enter a value bigger than {1}")]
public int Code { get; set; }
}
The UI validation is setup the usual out of the box way with unobtrusive JS validation plugin.
The issue: I have 2 navigation actions, back and next. Next is fine, validation fires when things are wrong, and when things are right i.e. .isValid() returns true, the data is passed to the DB service etc etc.
However when I press 'back' I have a requirement to validate the form/ViewModel differently prior to saving. I.e. make sure Code is a positive integer, but don't bother with the Required or StringLength validation.
So basically I want to validate fully on Next but partially on Back. Is that possible?
When I've done something similar in the past the easiest way i found was to use fluent validation http://fluentvalidation.codeplex.com/wikipage?title=mvc. You can pass parameters to the validator and switch to different rule sets.
I've used the following conditional "Required" & "StringLength" attributes in the past and they work well.
Required If Attribute:
using System;
using System.ComponentModel.DataAnnotations;
using System.Reflection;
namespace Website.Core.Mvc.DataAnnotations
{
[AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = true)]
public class RequiredIfAttribute : RequiredAttribute
{
public string OtherProperty { get; set; }
public object OtherPropertyValue { get; set; }
public RequiredIfAttribute(string otherProperty, object value)
: base()
{
OtherProperty = otherProperty;
OtherPropertyValue = value;
}
private object _TypeId = new object();
public override object TypeId
{
get
{
return _TypeId;
}
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
PropertyInfo property = validationContext.ObjectType.GetProperty(this.OtherProperty);
if (property == null)
{
return new ValidationResult(this.OtherProperty + " not found");
}
// Get
object actualOtherPropertyValue = property.GetValue(validationContext.ObjectInstance, null);
// If the other property matches the expected value then validate as normal
if (IsRequired(OtherPropertyValue, actualOtherPropertyValue))
{
// Call base and validate required as normal
ValidationResult isValid = base.IsValid(value, validationContext);
return isValid;
}
return ValidationResult.Success;
}
protected virtual bool IsRequired(object otherPropertyValue, object actualOtherPropertyValue)
{
return object.Equals(OtherPropertyValue, actualOtherPropertyValue);
}
}
}
String Length If Attribute:
using System.ComponentModel.DataAnnotations;
using System.Reflection;
namespace Website.Core.Mvc.DataAnnotations
{
public class StringLengthIfAttribute : StringLengthAttribute
{
public string OtherProperty { get; set; }
public object OtherPropertyValue { get; set; }
public StringLengthIfAttribute(int maximumLength, string otherProperty, object value)
: base(maximumLength)
{
OtherProperty = otherProperty;
OtherPropertyValue = value;
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
PropertyInfo property = validationContext.ObjectType.GetProperty(this.OtherProperty);
if (property == null)
{
return new ValidationResult(this.OtherProperty + " not found");
}
// Get
object actualOtherPropertyValue = property.GetValue(validationContext.ObjectInstance, null);
// If the other property matches the expected value then validate as normal
if (object.Equals(OtherPropertyValue, actualOtherPropertyValue))
{
// Call base and validate required as normal
return base.IsValid(value, validationContext);
}
return null;
}
}
}
Example Usage:
public class MyModel
{
[RequiredIf("IsBack", false)]
public string Name { get; set; }
public bool IsBack { get; set; }
}