What exactly triggers a Blazor component to redraw itself? - asp.net-mvc

I have Blazor WebAssembly application. I have a parent and child component that share a model class. When the child updates a value on the model, the child redraws itself, but the parent does not. So what exactly triggers a Blazor component to redraw itself? I can't find any documentation on this particular aspect of Blazor.
I have an POCO model class. In the real application this comes from a separate assembly. Models/AwesomenessModel.cs:
namespace BlazorApp.Models {
public class AwesomenessModel {
public int? Level { get; set; }
}
}
My index page. Pages/Index.razor:
#page "/"
#using BlazorApp.Components
#using BlazorApp.Models
<Awesomeness AwesomenessModel="#AwesomenessModel"></Awesomeness>
#code {
private AwesomenessModel AwesomenessModel = new AwesomenessModel();
}
The parent class. I have added a redraw button to verify that the content should have changed. Components/Awesomeness.razor:
#using BlazorApp.Models
#if (AwesomenessModel.Level.HasValue) {
<div>Awesomeness is #AwesomenessModel.Level.Value</div>
} else {
for (int i = 0; i < 10; i++) {
<AwesomenessSelector AwesomenessModel="#AwesomenessModel" Level="#i"></AwesomenessSelector>
}
}
<div><button class="btn btn-link" #onclick="RedrawClick">Redraw</button></div>
#code {
[Parameter]
public AwesomenessModel AwesomenessModel { get; set; }
private void RedrawClick() {
this.StateHasChanged();
}
}
The child component that does the actual change to the Awesomeness model. Components/AwesomenessSelector.razor:
#using BlazorApp.Models
#if (!AwesomenessModel.Level.HasValue) {
<div><button class="btn btn-link" #onclick="LevelClick">#Level</button></div>
}
#code {
[Parameter]
public AwesomenessModel AwesomenessModel { get; set; }
[Parameter]
public int Level { get; set; }
private void LevelClick() {
AwesomenessModel.Level = Level;
}
}
When I run the application and click on an awesomeness level, that level disappears (the AwesomenessSelector component redraws), but the rest are still visible and there is no 'Awesomeness is...' message (the Awesomeness component does not update). If I then click the Redraw button to force the Awesomeness component to redraw, I get the correct 'Awesomeness is...' message.
I'm probably not structuring the code in the correct way to get the parent component to redraw automatically. How should I structure it in this scenario?

Child Component add a EventCallback for OnLevelClick
#using BlazorApp.Models
#if (!AwesomenessModel.Level.HasValue) {
<div><button class="btn btn-link" #onclick="LevelClick">#Level</button></div>
}
#code {
[Parameter]
public AwesomenessModel AwesomenessModel { get; set; }
[Parameter]
public int Level { get; set; }
[Parameter]
public EventCallback<string> OnLevelClick { get; set; }
private void LevelClick() {
AwesomenessModel.Level = Level;
OnLevelClick.InvokeAsync();
}
}
parent Component add Handler for OnLevelClick
#using BlazorApp.Models
#if (AwesomenessModel.Level.HasValue) {
<div>Awesomeness is #AwesomenessModel.Level.Value</div>
} else {
for (int i = 0; i < 10; i++) {
<AwesomenessSelector AwesomenessModel="#AwesomenessModel" Level="#i" #OnLevelClick="OnLevelClick"></AwesomenessSelector>
}
}
#code {
[Parameter]
public AwesomenessModel AwesomenessModel { get; set; }
private void OnLevelClick() {
this.StateHasChanged();
}
}
hope this helps

You need StateHasChanged() on the parent control that doesn't know that the child has been updated. I believe you should create an event on the child that is triggered when you need to update the parent. Then on the parent when the event is triggered StateHasChanged(). All theory but should work.

Related

Blazor: How to use a component from 2 different pages with 2 different states

In a Blazor application, I want to use a component from two different pages. The component uses a service to maintain its state. But each page needs to have the component use a different state. Here's how I thought I would do it (using the default Counter component to demonstrate).
CounterService.cs
namespace TestTwoInterfaceToOneService.Shared
{
public interface ICounterServiceA
{
int CurrentCount { get; set; }
void IncrementCount();
}
public interface ICounterServiceB
{
int CurrentCount { get; set; }
void IncrementCount();
}
public class CounterService : ICounterServiceA, ICounterServiceB
{
public int CurrentCount { get; set; }
public void IncrementCount()
{
CurrentCount++;
}
}
}
In Program.cs add:
builder.Services.AddScoped<ICounterServiceA, CounterService>();
builder.Services.AddScoped<ICounterServiceB, CounterService>();
Counter.razor
<h1>Counter</h1>
<p>Current count: #CounterService.CurrentCount</p>
<button class="btn btn-primary" #onclick="IncrementCount">Click me</button>
#code {
[Parameter]
public CounterService CounterService { get; set; }
private void IncrementCount()
{
CounterService.CurrentCount++;
}
}
PageA.razor
#page "/pagea"
#inject ICounterServiceA CounterServiceA
<h3>Page A</h3>
<Counter CounterService="CounterServiceA" /> #*<-- Error: Cannot convert from ICounterServiceB to CounterService*#
PageB.razor
#page "/pageb"
#inject ICounterServiceB CounterServiceB
<h3>Page B</h3>
<Counter CounterService="CounterServiceB" /> #*<-- Error: Cannot convert from ICounterServiceB to CounterService*#
I get the error 'Cannot convert from ICounterServiceB to CounterService' when I try to pass the service to the component. I've discovered that using 2 identical interfaces pointing to the same concrete implementation does indeed give me 2 scoped instances. However, I cannot figure out how to pass those instances into the component.
Is there a piece that I'm missing? Or, should this be done some other way?
SOLUTION
Using a combination of the answer from Henk and the comments from Brian, the solution I came up with is:
CounterService.cs
namespace TestTwoInterfaceToOneService.Shared
{
public class CounterService
{
public int CurrentCount { get; set; }
public void IncrementCount()
{
CurrentCount++;
}
public void ResetCount()
{
CurrentCount = 0;
}
}
}
CounterStateService.cs
using System.Collections.Generic;
namespace TestTwoInterfaceToOneService.Shared
{
public interface ICounterStateService
{
CounterService this[string key] { get; }
}
public class CounterStateService : ICounterStateService
{
private Dictionary<string, CounterService> counterServices = new();
public CounterService this[string key]
{
get
{
if (!counterServices.ContainsKey(key))
{
counterServices.Add(key, new CounterService());
}
return counterServices[key];
}
}
}
}
In Program.cs, add
builder.Services.AddScoped<ICounterStateService, CounterStateService>();
Counter.razor
<p>Current count: #CounterService.CurrentCount</p>
<button class="btn btn-primary" #onclick="IncrementCount">Increment</button>
#code {
[Parameter]
public CounterService CounterService { get; set; }
private void IncrementCount()
{
CounterService.CurrentCount++;
}
}
PageA.razor
#page "/pagea"
#inject ICounterStateService CounterStateService
<h3>Page A</h3>
<Counter CounterService='CounterStateService["A"]' />
<button class="btn btn-primary" #onclick='CounterStateService["A"].ResetCount'>Reset Count</button>
PageB.razor
#page "/pageb"
#inject ICounterStateService CounterStateService
<h3>Page B</h3>
<Counter CounterService='CounterStateService["B"]' />
<button class="btn btn-primary" #onclick='CounterStateService["B"].ResetCount'>Reset Count</button>
Or, should this be done some other way?
Yes. Baking this usage pattern into the Type structure isn't going to adapt or scale well. What if you need a third page, or want to make it dynamic?
You could use a wrapper service and a scheme with a key to bind a State to a Component:
public class CounterService { ... } // no need to register
public class CounterStateService // register for injection
{
private Dictionary <string, CounterService> stateLookup = new();
public CounterService this[string key]
{
if (! stateLookup.tryGetValue (key, out CounterService service))
{
service = new CounterService();
stateLookup.Add(key, service);
}
return service;
}
}
and use it like
#page "/pagea"
#inject CounterStateService CounterStateService
<h3>Page A</h3>
<Counter CounterService="CounterStateService["A"]" />

Custom Validation for nested model in .net core

I am trying to validate a nested model using custom validation. But the problem is AttributeAdapterBase.AddValidation function is never called on nested model. However it works well with simple class property
Custom required validation attribute:
public interface IId
{
long Id { get; set; }
}
public class Select2RequiredAttribute : RequiredAttribute
{
public Select2RequiredAttribute(string errorMessage = "") : base()
{
ErrorMessage = errorMessage;
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
Type t = value.GetType();
if (typeof(IId).IsAssignableFrom(t))
{
if ((value as IId).Id == 0)
{
return new ValidationResult(ErrorMessage);
}
}
else
{
return new ValidationResult(ErrorMessage);
}
return ValidationResult.Success;
}
}
Attribute adapter base:
public class Select2RequiredAttributeAdapter : AttributeAdapterBase<Select2RequiredAttribute>
{
public Select2RequiredAttributeAdapter(Select2RequiredAttribute attribute, IStringLocalizer stringLocalizer) : base(attribute, stringLocalizer)
{
}
public override void AddValidation(ClientModelValidationContext context)
{
MergeAttribute(context.Attributes, "data-val", "true");
MergeAttribute(context.Attributes, "data-val-select2-required", GetErrorMessage(context));
}
public override string GetErrorMessage(ModelValidationContextBase validationContext)
{
return Attribute.ErrorMessage ?? GetErrorMessage(validationContext.ModelMetadata, validationContext.ModelMetadata.GetDisplayName());
}
}
Adapter provider:
public class Select2RequiredAdapterProvider : IValidationAttributeAdapterProvider
{
private readonly IValidationAttributeAdapterProvider _baseProvider = new ValidationAttributeAdapterProvider();
public IAttributeAdapter GetAttributeAdapter(ValidationAttribute attribute, IStringLocalizer stringLocalizer)
{
if (attribute is Select2RequiredAttribute)
{
return new Select2RequiredAttributeAdapter(attribute as Select2RequiredAttribute, stringLocalizer);
}
else
{
return _baseProvider.GetAttributeAdapter(attribute, stringLocalizer);
}
}
}
Startup.cs:
services.AddSingleton<IValidationAttributeAdapterProvider, Select2RequiredAdapterProvider>();
Model classes:
public interface IBaseBriefViewModel : IId
{
string Name { get; set; }
}
public class BaseBriefViewModel : IBaseBriefViewModel
{
public virtual long Id { get; set; }
public string Name { get; set; }
}
public class UserViewModel
{
public long Id { get; set; }
public string Name { get; set; }
[Select2Required("Branch is required.")]
public BaseBriefViewModel Branch { get; set; }
}
Branch select 2 partial view:
#model DataLibrary.ViewModels.BriefViewModels.BaseBriefViewModel
#{
var elementId = ViewData["ElementId"] != null && !string.IsNullOrEmpty(ViewData["ElementId"].ToString()) ? ViewData["ElementId"].ToString() : "branch-id";
}
<div class="form-group">
<label>Branch: <span class="text-danger"></span></label>
<div class="row">
<div class="#select2Class">
#Html.DropDownListFor(model => model.Id, new List<SelectListItem>() {
new SelectListItem()
{
Value = (Model!=null&&Model.Id>0)?Model.Id.ToString():"",
Text = (Model!=null&&Model.Id>0)?Model.Name:"",
Selected = (Model!=null&&Model.Id>0)?true:false,
}}, new { #id = elementId, #class = "form-control disable-field"})
#Html.ValidationMessageFor(model => model.Id, "", new { #class = "text-danger" })
</div>
</div>
</div>
<script>
$(function () {
var id = "#" + "#elementId";
var url = '/Branch/GetBranchsForSelect2';
var dataArray = function (params) {
params.page = params.page || 1;
return {
prefix: params.term,
pageSize: pageSize,
pageNumber: params.page,
};
};
Select2AutoCompleteAjax(id, url, dataArray, pageSize, "---Branch---");
});
</script>
All this code works well for server side. But for better user experience I want to show error before submitting form. How can I achieve this? I want to use this BaseBriefViewModel for a lot of Select2 in the project. So hard coding a static error message is not a good idea. What I really want to do is pass a error message from parent object. Like Branch is required in this specific case. Maybe in some other class I might pass Product is required
Any direction will be appreciated
At the moment this is not supported - but support is in planned. See dotnet github issue:
https://github.com/dotnet/runtime/issues/36093

How to retrive data from dynamic text box using Blazor

MyCustomControl.razor
<input type="text" id="#id" />
#code {
[Parameter]
public string id { get; set; }
}
Test.Razor
#page "/test"
<button #onclick="#addCompoment">add text box</button>
<div class="simple-list-list">
#if (componentListTest == null)
{
<p>You have no items in your list</p>
}
else
{
<ul>
#foreach (var item in componentListTest)
{
#item<br/>
}
</ul>
}
</div>
#functions {
private List<RenderFragment> componentListTest { get; set; }
private int currentCount { get; set; }
private string TxtExample { get; set; }
protected void OnInit()
{
currentCount = 0;
componentListTest = new List<RenderFragment>();
}
protected void addCompoment()
{
if(componentListTest==null)
{
componentListTest = new List<RenderFragment>();
}
componentListTest.Add(CreateDynamicComponent(currentCount));
currentCount++;
}
RenderFragment CreateDynamicComponent(int counter) => builder =>
{
try
{
var seq = 0;
builder.OpenComponent(seq, typeof(MyCustomControl));
builder.AddAttribute(++seq, "id", "listed-" + counter);
builder.CloseComponent();
}
catch (Exception ex)
{
throw;
}
};
}
After Adding the textbox dynamically,how to retrieve all input data from the textbox (after clicking on the submit button.)
How to interact with dynamic component and fetch Value.
MyCustomControl is component, Append in Test Razor Page.
for these component create an attribute like bind-value to get input field data given by user
There are a couple of solutions to this type of issue, depending on the general design of your app, constraints, and such like. The following solution is simple. Generally speaking, it involves passing the value of the added text box to a parent component to be saved in a list object. The parent component has a button that displays the list of text when clicked.
The following is the definition of the child component:
MyCustomControl.razor
<input type="text" #bind="#Value" id="#ID" />
#code {
private string _value;
public string Value
{
get { return _value; }
set
{
if (_value != value)
{
_value = value;
if (SetValue.HasDelegate)
{
SetValue.InvokeAsync(value);
}
}
}
}
[Parameter]
public string ID { get; set; }
[Parameter]
public EventCallback<string> SetValue { get; set; }
}
Usage in a parent component
<button #onclick="#addCompoment">add text box</button>
<div class="simple-list-list">
#if (componentListTest == null)
{
<p>You have no items in your list</p>
}
else
{
<ul>
#foreach (var item in componentListTest)
{
#item
<br />
}
</ul>
}
</div>
<p><button #onclick="#ShowValues">Show values</button></p>
#if (Display)
{
<ul>
#foreach (var value in values)
{
<li>#value</li>
}
</ul>
}
#code {
public void SetValue(string value)
{
values.Add(value);
}
private List<RenderFragment> componentListTest { get; set; }
private List<string> values = new List<string>();
private int currentCount { get; set; }
protected override void OnInitialized()
{
currentCount = 0;
componentListTest = new List<RenderFragment>();
}
private bool Display;
private void ShowValues()
{
if (values.Any())
{
Display = true;
}
}
protected void addCompoment()
{
if (componentListTest == null)
{
componentListTest = new List<RenderFragment>();
}
componentListTest.Add(CreateDynamicComponent(currentCount));
currentCount++;
}
RenderFragment CreateDynamicComponent(int counter) => builder =>
{
try
{
builder.OpenComponent(0, typeof(MyCustomControl));
builder.AddAttribute(1, "id", "listed-" + counter);
builder.AddAttribute(2, "SetValue", Microsoft.AspNetCore.Components.CompilerServices.RuntimeHelpers.TypeCheck<Microsoft.AspNetCore.Components.EventCallback<System.String>>(Microsoft.AspNetCore.Components.EventCallback.Factory.Create<System.String>(this, this.SetValue )));
builder.CloseComponent();
}
catch (Exception ex)
{
throw;
}
};
}
Note:
Notice the SetValue attribute I've added to the CreateDynamicComponent's builder. This provides a Component Parameter to MyCustomControl of type EventCallback<string> which is assigned to the SetValue parameter property:
[Parameter]
public EventCallback<string> SetValue { get; set; }
And it is used (trigger the method which is also called SetValue in the parent component. You can change the name if you like) to pass the changed value from the child component to the parent component.
Use code instead of functions.
Note that I've made some modifications in your code: OnInitialized instead of OnInit (obsolete), sequence numbers should not created the way you do. Refer to this article written by Steve Sanderson ...
Hope this helps...

WP8 - Bind ProgressBar visibility

I have a simple thing to code, i checked other questions but couldn't it yet.
I have an application which loads some data from an xml file retrieved from the web, and then displays it inside a longlistselector.
I did it, it works, now i would like to add an indeterminate progressbar which stays active until I finished the data loading.
I enclosed the progressbar in a stackpanel, before my longlistselector, and i bound its visibility to the function ProgressBarVisibility (see code below).
<phone:PivotItem Header="Status">
<StackPanel>
<ProgressBar Value ="0" IsIndeterminate="True" Visibility="{Binding ProgressBarVisibility}"/>
<phone:LongListSelector Margin="0,0,-12,0" ItemsSource="{Binding PivotOne}">
<phone:LongListSelector.ItemTemplate>
<!-- lots of code here -->
</phone:LongListSelector.ItemTemplate>
</phone:LongListSelector>
</StackPanel>
</phone:PivotItem>
In the MainViewModel.cs , that's how i wrote the thing.
using System.Windows;
public class MainViewModel : INotifyPropertyChanged
{
public MainViewModel()
{
this.PivotOne = new ObservableCollection<ItemViewModel>();
this.PivotTwo = new ObservableCollection<ItemViewModel>();
this.PivotThree = new ObservableCollection<ItemViewModel>();
}
/// <summary>
/// A collection for ItemViewModel objects.
/// </summary>
public ObservableCollection<ItemViewModel> PivotOne { get; private set; }
public ObservableCollection<ItemViewModel> PivotTwo { get; private set; }
public ObservableCollection<ItemViewModel> PivotThree { get; private set; }
private string _detailPageTitle = "Default";
/// <summary>
/// DetailPageTitle ritorna il titolo della pagina di dettaglio. Viene settato nella funzione che carica la pagina secondaria
/// </summary>
/// <returns></returns>
public string DetailPageTitle
{
get
{
return _detailPageTitle;
}
set
{
if (value != _detailPageTitle)
{
_detailPageTitle = value;
NotifyPropertyChanged("DetailPageTitle");
}
}
}
public bool IsDataLoaded
{
get;
private set;
}
private Visibility _progressBarVisibility = Visibility.Collapsed;
public Visibility ProgressBarVisibility
{
get
{
return _progressBarVisibility;
}
set
{
if (value != _progressBarVisibility)
{
_progressBarVisibility = value;
NotifyPropertyChanged("ProgressBarVisibility");
}
}
}
private Visibility _progressBarVisibility = Visibility.Visible;
public Visibility ProgressBarVisibility
{
get
{
return _progressBarVisibility;
}
set
{
if (value != _progressBarVisibility)
{
_progressBarVisibility = value;
NotifyPropertyChanged("ProgressBarVisibility");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (null != handler)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
public void LoadData()
{
//progressbar is visible, data not loaded
this.IsDataLoaded = false;
ProgressBarVisibility = Visibility.Visible;
// Load Static and dynamic data -- populate the different pivots
LoadStaticData();
LoadXMLFile();
// data loaded, progressbar collapsed
this.IsDataLoaded = true;
ProgressBarVisibility = Visibility.Collapsed;
}
So i included system.windows library, and used the visibility class.
Anyway, i cannot get the progressbar to disappear when the loading is done, it keeps going.
Any suggestion? where am i doing it wrong?
Thanks in advance!
Solution: loaddata is executed on the app activation, so the content is not even rendered at that moment.
Your MainViewModel must implement INotifyPropertyChanged to signal to the View that one of the properties has changed. In addition, when you change the ProgressBarVisibility property, it should fire the PropertyChanged event.
There are a number of MVVM frameworks that come with some implementation of INotifyPropertyChanged, but you could easily implement something simple yourself.
You need to report the changed made to the view:
Change
public Visibility ProgressBarVisibility { get; set; }
by
private Visibility _progressBarVisibility;
public Visibility ProgressBarVisibility
{
get { return _progressBarVisibility;}
set { _progressBarVisibility = value; RaisePropertyChanged("ProgressBarVisibility");}
}
Make sure you implement INotifyPropertyChanged or a base ViewModel that implement it (MVVMLigth : ViewModelBase).

Client side validation for my dropdown populated with enum values

I have a view where I use a dropdown list with enum:
public enum MaterialWorthEnumViewModel
{
[Display(Name = "")] Undefined,
[Display(Name = "< 1.000€")] LessThan1000,
[Display(Name = "1.000€ < 10.000€")] Between1000And10000,
[Display(Name = "10.000€ < 100.000€")] Between10000And100000,
[Display(Name = "100.000€ < 25.000.000€")] Between100000And25000000,
[Display(Name = "> 25.000.000€")] GreaterThan250000000,
}
I use a view model with this view:
public class MaterialEditNewViewModel
{
public int RequestID { get; set; }
...
[EnumRequired]
public MaterialWorthEnumViewModel MaterialWorth { get; set; }
}
As you can see above, I used a custom validation [EnumRequired] I grab the code from a blog online.
[AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = false)]
public class EnumRequiredAttribute : RequiredAttribute
{
private const string UNDEFINED_VALUE = "Undefined";
public string UndefinedValue { get; set; }
public EnumRequiredAttribute() : this(UNDEFINED_VALUE)
{ }
public EnumRequiredAttribute(string undefinedValue) : base()
{
if (String.IsNullOrWhiteSpace(undefinedValue))
{
throw new ArgumentNullException("undefinedValue");
}
UndefinedValue = undefinedValue;
}
public override bool IsValid(object value)
{
if (value == null)
{
return false;
}
var undefined = Enum.Parse(value.GetType(), UndefinedValue);
return !Enum.Equals(value, undefined);
}
}
Below is for the client side validation
public class ModelClientValidationEnumRequiredRule : ModelClientValidationRule
{
public ModelClientValidationEnumRequiredRule(string errorMessage, string undefinedValue)
{
base.ErrorMessage = errorMessage;
base.ValidationType = "enumrequired";
base.ValidationParameters.Add("undefinedvalue", undefinedValue);
}
}
public class EnumRequiredAttributeAdapter : DataAnnotationsModelValidator<EnumRequiredAttribute>
{
public EnumRequiredAttributeAdapter(ModelMetadata metadata, ControllerContext context, EnumRequiredAttribute attribute)
: base(metadata, context, attribute)
{ }
public override IEnumerable<ModelClientValidationRule> GetClientValidationRules()
{
return new ModelClientValidationEnumRequiredRule[]
{
new ModelClientValidationEnumRequiredRule(base.ErrorMessage, Attribute.UndefinedValue)
};
}
}
Below is the javascript for the client side validation
Sys.Mvc.ValidatorRegistry.validators.enumrequired = function (rule) {
var undefinedValue = rule.ValidationParameters.undefinedvalue;
return function (value, context) {
return value != undefinedValue;
}
}
I also updated my GLobal.asax file:
DataAnnotationsModelValidatorProvider.RegisterAdapter(typeof(EnumRequiredAttribute), typeof(EnumRequiredAttributeAdapter));
The validation works pretty well on the server side but the client side validation is never triggered. So when I didn't choose any value on my view for my dropdown enum, I reach the action in the controller and then the server side validation occured and I go back to the view. I concluded that the client side validation didn't occurred.
Does someone can help me doing valid client side validation for this dropdown enum ?
Thanks. I'm a bit lost.
I don't see any relationship between your EnumRequiredAttribute and the other 2 classes. If you are using ASP.NET MVC 3 you need to associate your custom validation attribute with the adapter. This could be done in Application_Start:
DataAnnotationsModelValidatorProvider.RegisterAdapter(
typeof(EnumRequiredAttribute),
typeof(EnumRequiredAttributeAdapter)
);
Also on your client side you have shown some js code that relies on Microsoft*.js libraries. Those are now obsolete and should no longer be used. The default standard in ASP.NET MVC 3 for client side validation is the jquery.validate plugin.
So let's take an example.
Model:
public class MyViewModel
{
[EnumRequired]
public MaterialWorthEnumViewModel MaterialWorth { get; set; }
}
Controller:
public class HomeController : Controller
{
public ActionResult Index()
{
return View(new MyViewModel());
}
[HttpPost]
public ActionResult Index(MyViewModel model)
{
return View(model);
}
}
View (Index.cshtml):
#model MyViewModel
<script src="#Url.Content("~/Scripts/jquery.validate.js")" type="text/javascript"></script>
<script src="#Url.Content("~/Scripts/jquery.validate.unobtrusive.js")" type="text/javascript"></script>
<script src="#Url.Content("~/Scripts/enumrequiredadapter.js")" type="text/javascript"></script>
#using (Html.BeginForm())
{
#Html.LabelFor(x => x.MaterialWorth)
#Html.EditorFor(x => x.MaterialWorth)
#Html.ValidationMessageFor(x => x.MaterialWorth)
<button type="submit">OK</button>
}
and finally the enumrequiredadapter.js adapter:
(function ($) {
$.validator.unobtrusive.adapters.add('enumrequired', ['undefinedvalue'], function (options) {
options.rules['enumrequired'] = options.params;
if (options.message != null) {
options.messages['enumrequired'] = options.message;
}
});
$.validator.addMethod('enumrequired', function (value, element, params) {
return value != params.undefinedvalue;
});
})(jQuery);
Also don't forget to remove all traces of Microsoft*.js script references from your site. And that's pretty much it.

Resources