@using DevExtreme.NETCore.Demos.ViewModels
@using(Html.BeginForm()) {
using(Html.DevExtreme().ValidationGroup()) {
@Html.AntiForgeryToken()
@(Html.DevExtreme().Form<EditorsViewModel>()
.OnInitialized("onInitialized")
.OnOptionChanged("onOptionChanged")
.ShowValidationSummary(true)
.Items(items => {
items.AddGroup()
.Caption("Credentials")
.Items(groupItems => {
groupItems.AddSimpleFor(m => m.Email)
.Editor(e => e.TextBox()
.ValueChangeEvent("keyup")
);
groupItems.AddSimpleFor(m => m.Password)
.Editor(e => e.TextBox()
.Mode(TextBoxMode.Password)
.InputAttr("aria-label", "Password")
.OnValueChanged("passwordChanged")
.ValueChangeEvent("keyup")
.Buttons(buttons => {
buttons.Add()
.Name("password")
.Location(TextEditorButtonLocation.After)
.Widget(w => w.Button()
.Type(ButtonType.Default)
.Icon("eyeopen")
.StylingMode(ButtonStylingMode.Text)
.OnClick("() => changePasswordMode('Password')")
);
})
);
groupItems.AddSimpleFor(m => m.ConfirmPassword)
.Editor(e => e.TextBox()
.Mode(TextBoxMode.Password)
.InputAttr("aria-label", "Password")
.ValueChangeEvent("keyup")
.Buttons(buttons => {
buttons.Add()
.Name("password")
.Location(TextEditorButtonLocation.After)
.Widget(w => w.Button()
.Type(ButtonType.Default)
.Icon("eyeopen")
.StylingMode(ButtonStylingMode.Text)
.OnClick("() => changePasswordMode('ConfirmPassword')")
);
})
);
});
items.AddGroup()
.Caption("Personal Data")
.Items(groupItems => {
groupItems.AddSimpleFor(m => m.Name)
.Editor(e => e.TextBox()
.ValueChangeEvent("keyup")
);
groupItems.AddSimpleFor(m => m.Date)
.Editor(e => e
.DateBox()
.OpenOnFieldClick(true)
.Placeholder("Birth Date")
.AcceptCustomValue(false)
);
groupItems.AddSimpleFor(m => m.VacationDates)
.Editor(e => e
.DateRangeBox()
.StartDatePlaceholder("Start Date")
.AcceptCustomValue(false)
.EndDatePlaceholder("End Date")
)
.ValidationRules(validationRule => {
validationRule.AddCustom()
.Message("The vacation period must not exceed 25 days")
.ValidationCallback("validateVacationDatesRange");
validationRule.AddCustom()
.Message("Both start and end dates must be selected")
.ValidationCallback("validateVacationDatesPresence");
});
});
items.AddGroup()
.Caption("Billing address")
.Items(groupItems => {
groupItems.AddSimpleFor(m => m.Country)
.Editor(e => e
.SelectBox()
.InputAttr("aria-label", "Country")
.DataSource(d => d.Mvc().Controller("GeoNames").LoadAction("Countries"))
);
groupItems.AddSimpleFor(m => m.City)
.Editor(e => e
.Autocomplete()
.ValueChangeEvent("keyup")
.MinSearchLength(2)
.DataSource(d => d.Mvc().Controller("GeoNames").LoadAction("Cities"))
);
groupItems.AddSimpleFor(m => m.Address)
.Editor(e => e.TextBox()
.ValueChangeEvent("keyup")
);
groupItems.AddSimpleFor(m => m.Phone)
.HelpText("Enter the phone number in USA phone format")
.Editor(e => e.TextBox()
.Mask("+1 (X00) 000-0000")
.InputAttr("aria-label", "Phone")
.ValueChangeEvent("keyup")
.MaskRules(new { X = new JS("/[02-9]/") })
.MaskInvalidMessage("The phone must have a correct USA phone format")
);
});
items.AddGroup()
.CssClass("last-group")
.ColCountByScreen(c => c.Md(2).Sm(2).Lg(2))
.Items(groupItems => {
groupItems.AddSimpleFor(m => m.Accepted)
.Label(l => l.Visible(false))
.Editor(editor => editor.CheckBox()
.Width(270)
.Text("I agree to the Terms and Conditions")
);
groupItems.AddGroup()
.ColCountByScreen(c => c.Md(2).Sm(2).Lg(2))
.CssClass("buttons-group")
.Items(secondGroupItems => {
secondGroupItems.AddButton().Name("Reset")
.ButtonOptions(b => b.Text("Reset")
.Icon(Url.Content("refresh"))
.Width(120)
.Disabled(true)
.OnClick("onResetButtonClick")
);
secondGroupItems.AddButton()
.ButtonOptions(b => b.Text("Register")
.Type(ButtonType.Default)
.Width(120)
.UseSubmitBehavior(true)
);
});
});
})
.FormData(Model)
)
}
}
<script>
let formInstance;
function onInitialized(e) {
formInstance = e.component;
}
function onOptionChanged(e) {
if(e.name === 'isDirty') {
const resetButton = formInstance.getButton('Reset');
resetButton.option('disabled', !e.value);
}
}
function onResetButtonClick(e) {
formInstance.reset();
}
function passwordChanged(e) {
const editor = formInstance.getEditor('ConfirmPassword');
if (editor.option('value')) {
editor.element().dxValidator('validate');
}
}
function changePasswordMode(name) {
let editor = formInstance.getEditor(name);
editor.option('mode', editor.option('mode') === 'text' ? 'password' : 'text');
}
function validateVacationDatesRange({ value }) {
const [startDate, endDate] = value;
if (startDate === null || endDate === null) {
return true;
}
const millisecondsPerDay = 24 * 60 * 60 * 1000;
const daysDifference = Math.abs((endDate - startDate) / millisecondsPerDay);
return daysDifference < 25;
}
function validateVacationDatesPresence({ value }) {
const [startDate, endDate] = value;
if (startDate === null && endDate === null) {
return true;
}
return startDate !== null && endDate !== null;
}
</script>
<p>The submitted data has been successfully accepted.</p>
<br />
@(Html.DevExtreme().Button()
.Text("Reload demo")
.Type(ButtonType.Default)
.Icon("refresh")
.OnClick(@<text>
function() {
window.location = window.location;
}
</text>)
)
using DevExtreme.AspNet.Data;
using DevExtreme.AspNet.Mvc;
using DevExtreme.NETCore.Demos.Models.SampleData;
using DevExtreme.NETCore.Demos.ViewModels;
using Microsoft.AspNetCore.Mvc;
using System;
using System.Collections.Generic;
using System.Linq;
namespace DevExtreme.NETCore.Demos.Controllers {
public class FormController : Controller {
[HttpGet]
public ActionResult Validation() {
return View(new EditorsViewModel() {
Name = "Peter"
});
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Validation(EditorsViewModel userInfo) {
if(ModelState.IsValid) {
return View("SuccessValidation");
}
return View(userInfo);
}
}
}
using DevExtreme.AspNet.Data;
using DevExtreme.AspNet.Mvc;
using DevExtreme.NETCore.Demos.Models.SampleData;
using Microsoft.AspNetCore.Mvc;
using System;
using System.Linq;
using System.Net.Http;
namespace DevExtreme.NETCore.Demos.Controllers.ApiControllers {
[Route("api/[controller]/[action]")]
public class GeoNamesController : Controller {
[HttpGet]
public object Countries(DataSourceLoadOptions loadOptions) {
return DataSourceLoader.Load(SampleData.Countries, loadOptions);
}
[HttpGet]
public object Cities(DataSourceLoadOptions loadOptions) {
return DataSourceLoader.Load(SampleData.Cities, loadOptions);
}
}
}
using System;
using System.Linq;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Caching.Memory;
using DevExtreme.NETCore.Demos.Models.DataGrid;
namespace DevExtreme.NETCore.Demos.Controllers {
public class RemoteValidationController : Controller {
InMemoryEmployeesValidationDataContext _db;
public RemoteValidationController(IHttpContextAccessor httpContextAccessor, IMemoryCache memoryCache) {
_db = new InMemoryEmployeesValidationDataContext(httpContextAccessor, memoryCache);
}
[HttpPost]
public JsonResult CheckUniqueEmailAddress(EmployeeValidation model) {
var isValid = !_db.Employees.Any(emp => {
var equals = string.Equals(emp.Email, model.Email, StringComparison.OrdinalIgnoreCase);
return model.ID != emp.ID && equals;
});
return Json(isValid);
}
[HttpPost]
public JsonResult CheckEmailAddress(string email) {
var isValid = !string.Equals(email, "test@dx-email.com", StringComparison.OrdinalIgnoreCase);
return Json(isValid);
}
}
}
using DevExtreme.AspNet.Mvc;
using Microsoft.AspNetCore.Mvc;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
namespace DevExtreme.NETCore.Demos.ViewModels {
public class EditorsViewModel {
[Required(ErrorMessage = "Email is required")]
[RegularExpression(@"^[\d\w._-]+@[\d\w._-]+\.[\w]+$", ErrorMessage = "Email is invalid")]
[Remote("CheckEmailAddress", "RemoteValidation", ErrorMessage = "Email is already registered", HttpMethod = "POST")]
public string Email { get; set; } = string.Empty;
[Required(ErrorMessage = "Name is required")]
[RegularExpression(@"^[^0-9]+$", ErrorMessage = "Do not use digits in the Name.")]
[StringLength(int.MaxValue, MinimumLength = 2, ErrorMessage = "Name must have at least 2 symbols")]
public string Name { get; set; } = string.Empty;
[Required(ErrorMessage = "Password is required")]
public string Password { get; set; } = string.Empty;
[Required(ErrorMessage = "Confirm Password is required")]
[System.ComponentModel.DataAnnotations.Compare("Password", ErrorMessage = "'Password' and 'Confirm Password' do not match.")]
public string ConfirmPassword { get; set; } = string.Empty;
[RegularExpression(@"^[02-9]\d{9}$", ErrorMessage = "The phone must have a correct USA phone format")]
public string Phone { get; set; } = string.Empty;
public string Extension { get; set; }
[Required(ErrorMessage = "Country is required")]
public string Country { get; set; }
[Required(ErrorMessage = "Address is required")]
public string Address { get; set; } = string.Empty;
public string Description { get; set; }
public int Age { get; set; }
public string Drink { get; set; }
[Required(ErrorMessage = "City is required")]
[RegularExpression("^[^0-9]+$", ErrorMessage = "Do not use digits in the City name.")]
[StringLength(int.MaxValue, MinimumLength = 2, ErrorMessage = "City must have at least 2 symbols")]
public string City { get; set; }
public IEnumerable<string> Colors { get; set; }
public IEnumerable<string> SelectedColors { get; set; }
public string Color { get; set; }
[Display(Name = "Date of birth")]
[Required(ErrorMessage = "Date of birth is required")]
[VerifyAge(21, ErrorMessage = "You must be at least {1} years old")]
public DateTime? Date { get; set; }
[VerifyDateRange(25, ErrorMessage = "The vacation period must not exceed {1} days")]
public DateTime?[] VacationDates { get; set; }
[DevExtremeRequired(ErrorMessage = "You must agree to the Terms and Conditions")]
public bool Accepted { get; set; }
}
}
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
namespace DevExtreme.NETCore.Demos.ViewModels {
public class VerifyAgeAttribute : ValidationAttribute, IClientModelValidator {
public VerifyAgeAttribute(int age) {
Age = age;
}
public int Age { get; private set; }
protected override ValidationResult IsValid(object value, ValidationContext validationContext) {
if((DateTime?)value <= DateTime.Now.AddYears(-Age)) {
return ValidationResult.Success;
}
return new ValidationResult(FormatErrorMessage(validationContext.DisplayName));
}
void IClientModelValidator.AddValidation(ClientModelValidationContext context) {
context.Attributes.Add("data-val-custom-verifyage", FormatErrorMessage(context.ModelMetadata.GetDisplayName()));
context.Attributes.Add(
"data-val-custom-verifyage-validationcallback",
$@"function(options) {{
var now = new Date();
return options.value && options.value <= now.setFullYear(now.getFullYear() - {Age});
}}");
}
public override string FormatErrorMessage(string name) {
return string.Format(ErrorMessageString, name, Age);
}
}
}
form {
margin: 10px 10px 15px;
}
.last-group {
margin-top: 30px;
margin-bottom: 10px;
}
.last-group .dx-item-content {
align-items: start;
justify-content: center;
}
.last-group .dx-field-item {
padding: 0 !important;
}
.buttons-group {
display: flex;
width: 100%;
justify-content: end;
}
.buttons-group .dx-item-content {
gap: 10px;
}