Ticket Q473947
Visible to All Users
Duplicate

We have closed this ticket because another page addresses its subject:

Validation - Warn end-users about mistakes/inconsistencies without preventing them from saving changes

Confirmable validation rules mechanism

created 12 years ago

I need to implement a confirmable validation rules mechanism, which would mimick regular validation rules in WinForms
but in Web environment
when the rule is broken, the user gets a confirmation message "Are you sure to save it?"

As many objects in my project are commited from within the code, I cannot simply set the confirmation messages as it is shown in the last post of http://www.devexpress.com/Support/Center/Question/Details/Q380252 thread.
In my opinion the best direction in my case is to use ObjectSpace_Committing event. Can you agree?

I am trying the following code:

C#
public class WebConfirmableRuleCriteriaViewController : ViewController { protected override void OnActivated() { base.OnActivated(); View.ObjectSpace.Committing += ObjectSpace_Committing; } protected override void OnDeactivated() { View.ObjectSpace.Committing -= ObjectSpace_Committing; base.OnDeactivated(); } protected void ObjectSpace_Committing(object sender, System.ComponentModel.CancelEventArgs e) { foreach (var modifiedObject in View.ObjectSpace.ModifiedObjects) { if (View.ObjectSpace.IsObjectToSave(modifiedObject)) { // if(modifiedObject has OptionalRuleAttribute && the rule is broken) { WebWindow.CurrentRequestWindow.RegisterClientScript("teset", "if(confirm('Are you sure to save? The rule is broken.')) { alert('Confirmed! Want to invoke ObjectSpace.CommitChanges.'); } else { alert('Cancelled! No save...'); } "); e.Cancel = true; } } } } }

Questions:

  1. I really doubt it, but is it possible to synchronously wait for the user until he confirms the JS dialog and then continue the server code?
    And get the user's answer to server side?
    The problem appears when I click Save&Close button: after the commit is cancelled, view is closed and the objectSpace is lost. So when the user confirms JS dialog I already have the changes lost.

  2. How to invoke ObjectSpace.CommitChanges via Javascript?

Answers approved by DevExpress Support

created 12 years ago (modified 12 years ago)

Hi Krzysztof.
Thank you for contacting us. I am afraid we cannot provide you with the detailed instructions on how to implement this functionality. It requires an additional research that we will perform when decide to implement the Validation - Warn end-users about mistakes/inconsistencies without preventing them from saving changes suggestion.
>>>is it possible to synchronously wait for the user until he confirms the JS dialog and then continue the server code?
No. A web request processing must be finished before the page is sent to the end user browser.
>>>And get the user's answer to server side?
>>>How to invoke ObjectSpace.CommitChanges via Javascript?
To execute a server-side XAF code from a client-side script, use the approach described in ticket Use Js to fire Xaf Simple Action.

    Other Answers

    created 12 years ago (modified 12 years ago)

    Here is a working Web solution! Thumbs up!
    Credits to:
    Apostolis Bekiaris (I used as a base the code from Xpand framework, Validation module)
    Michael (who directed me in a right way)
    The mechanism is simple:
    - there are 2 new validation rule types: WARNING and INFORMATION
    - if any warning or information rule is broken (XAF Web only):
        + confirmation ("Are you sure?") dialog shows up
        + relevant property editors get decorated by warning/info icons
    NOTE:
    This is solution for XAF Web. In all other environments those rules are treated as normal validation errors.
    However, it can be easily extended to XAF Win.
    Example of use:
    (see attachments in comments for the latest version!)

    C#
    [RuleCombinationOfPropertiesIsUnique("Unique_Opis2Opis3_for_Projekt", DefaultContexts.Save, "Opis2;Opis3", CustomMessageTemplate="Pair of fields \"Opis2\" and \"Opis3\" should be unique.")] [RuleErrorType("Unique_Opis2Opis3_for_Projekt", RuleType.Warning)] [RuleCriteria("ruleCriteria_test", "Save;"+RuleTypeController.ObjectSpaceObjectChanged, "Status != 'Odrzucony'", "\"Status\" is rather wrong....", UsedProperties = "Status")] [RuleErrorType("ruleCriteria_test", RuleType.Warning)] [RuleErrorType("ProjektConditionRule_Projekt", RuleType.Information)] public class Projekt : BaseObject { (...) private String m_opis; [RuleRequiredField("Required_InformationProperty_for_Projekt", "Save;" + RuleTypeController.ObjectSpaceObjectChanged, CustomMessageTemplate = "We are informing you that field \"Opis\" could be filled in")] [RuleErrorType("Required_InformationProperty_for_Projekt", RuleType.Information)] [Size(50)] public String Opis { get { return m_opis; } set { SetPropertyValue("Opis", ref m_opis, value); } } [CodeRule] public class ProjektConditionRule : RuleBase { public ProjektConditionRule() : base("", "Save", typeof(Projekt)) { } public ProjektConditionRule(IRuleBaseProperties properties) : base(properties) { } protected override bool IsValidInternal(object target, out string errorMessageTemplate) { errorMessageTemplate = string.Empty; Projekt item = target as Projekt; if (item == null) return true; if (item.DataRozpoczecia.Date == item.DataZakonczenia.Date) { errorMessageTemplate = "DataRozpoczecia shouldn't be the same as DataZakonczenia."; return false; } return true; } public override System.Collections.ObjectModel.ReadOnlyCollection<string> UsedProperties { get { return new System.Collections.ObjectModel.ReadOnlyCollection<string>(new List<string>() { "DataRozpoczecia", "DataZakonczenia" }); } } } }

    The solution code:
    ActionExecuteValidationController.cs

    C#
    using System; using System.Collections; using System.Collections.Generic; using System.Linq; using System.Reflection; using DevExpress.ExpressApp; using DevExpress.ExpressApp.Actions; using DevExpress.ExpressApp.Model.Core; using DevExpress.ExpressApp.Utils; using DevExpress.ExpressApp.Validation; using DevExpress.ExpressApp.Validation.AllContextsView; using DevExpress.Persistent.Base; using DevExpress.Persistent.Validation; namespace SampleProject.Module.Infrastructure.ConfirmableValidationRules { public class ActionExecuteValidationController : ObjectViewController { public event EventHandler<CustomGetAggregatedObjectsToValidateEventArgs> CustomGetAggregatedObjectsToValidate; public event EventHandler<NeedToValidateObjectEventArgs> NeedToValidateObject; public event EventHandler<ContextValidatingEventArgs> ContextValidating; protected virtual void OnContextValidating(ContextValidatingEventArgs args) { if (ContextValidating != null) { ContextValidating(this, args); } } protected void CustomizeDeleteValidationException(ValidationCompletedEventArgs args) { args.Exception.MessageHeader = ValidationExceptionLocalizer.GetExceptionMessage(ValidationExceptionId.DeleteErrorMessageHeader); args.Exception.ObjectHeaderFormat = ValidationExceptionLocalizer.GetExceptionMessage(ValidationExceptionId.DeleteErrorMessageObjectFormat); // No setter? //args.Exception.Message = args.Exception.MessageHeader + "\r\n" + args.Exception.Result.GetFormattedErrorMessage(args.Exception.ObjectHeaderFormat); // No problem! FieldInfo field = typeof(ValidationException).GetField("message", BindingFlags.Instance | BindingFlags.NonPublic); field.SetValue(args.Exception, args.Exception.MessageHeader + "\r\n" + new MessageHelper(Application, args.Exception.Result).GetFormattedErrorMessage(args.Exception.ObjectHeaderFormat)); } protected override void OnDeactivated() { base.OnDeactivated(); foreach (var controller in Frame.Controllers) { foreach (var action in controller.Actions) { action.Executed -= ActionOnExecuted; } } } protected override void OnActivated() { base.OnActivated(); foreach (var controller in Frame.Controllers) { foreach (var action in controller.Actions) { action.Executed += ActionOnExecuted; } } } protected void ActionOnExecuted(object sender, ActionBaseEventArgs actionBaseEventArgs) { if (View.ObjectTypeInfo.Type != typeof(ValidationResults)) { ValidationTargetObjectSelector deleteSelector = new ActionExecuteContextTargetObjectSelector(); SubscribeSelectorEvents(deleteSelector); var selectedObjects = ((SimpleActionExecuteEventArgs)actionBaseEventArgs).SelectedObjects; var context = actionBaseEventArgs.Action.Id; var contextValidatingEventArgs = new ContextValidatingEventArgs(context, new ArrayList(selectedObjects)); OnContextValidating(contextValidatingEventArgs); if (View.ObjectTypeInfo.IsPersistent && CanAccessDeletedObjects(context)) Validator.RuleSet.ValidateAll(contextValidatingEventArgs.TargetObjects, context, CustomizeDeleteValidationException); } } bool CanAccessDeletedObjects(string context) { return !(context == "Delete" && !ObjectSpace.IsDeletionDefferedType(View.ObjectTypeInfo.Type)); } private void OnSelectorCustomGetAggregatedObjectsToValidate(object sender, CustomGetAggregatedObjectsToValidateEventArgs args) { if (CustomGetAggregatedObjectsToValidate != null) { CustomGetAggregatedObjectsToValidate(this, args); } } private void OnSelectorNeedToValidateObject(object sender, NeedToValidateObjectEventArgs args) { if (NeedToValidateObject != null) { NeedToValidateObject(this, args); } } private void SubscribeSelectorEvents(ValidationTargetObjectSelector selector) { selector.CustomNeedToValidateObject += OnSelectorNeedToValidateObject; selector.CustomGetAggregatedObjectsToValidate += OnSelectorCustomGetAggregatedObjectsToValidate; } internal class MessageHelper { private const string defaultErrorMessageItemsSeparator = "\r\n - "; private XafApplication application; private RuleSetValidationResult result; public MessageHelper(XafApplication application, RuleSetValidationResult result) { this.application = application; this.result = result; } public string GetFormattedErrorMessage(string objectHeaderFormat) { return GetFormattedErrorMessage(objectHeaderFormat, defaultErrorMessageItemsSeparator); } public string GetFormattedErrorMessage(string objectHeaderFormat, string itemsSeparator) { List<string> objectMessages = new List<string>(); Dictionary<object, IList<RuleSetValidationResultItem>> resultsByTarget = result.GetResultsByTargets(); ArrayList orderedTargets = new ArrayList(resultsByTarget.Keys); orderedTargets.Sort(new ObjectDisplayTextComparer()); Dictionary<RuleType, IList<string>> currentObjectMessagesByType = new Dictionary<RuleType, IList<string>>(); foreach (RuleType ruleType in typeof(RuleType).GetEnumValues()) currentObjectMessagesByType.Add(ruleType, new List<string>()); foreach (object currentTarget in orderedTargets) { bool objectProcessed = false; List<string> currentObjectMessages = new List<string>(); string currentObjectHeader = ""; foreach (RuleSetValidationResultItem currentResult in resultsByTarget[currentTarget]) { if (currentResult.State == ValidationState.Invalid) { if (!objectProcessed) { if (!string.IsNullOrEmpty(objectHeaderFormat)) { currentObjectHeader = string.Format(objectHeaderFormat, ReflectionHelper.GetObjectDisplayText(currentTarget)); } objectProcessed = true; } RuleType ruleType = GetRuleType(currentResult); currentObjectMessagesByType[ruleType].Add(GetErrorPrefix(ruleType) + currentResult.ErrorMessage); } } foreach (IList<string> items in currentObjectMessagesByType.Values) currentObjectMessages.AddRange(items); if (currentObjectMessages.Count > 0) { //currentObjectMessages.Sort(); currentObjectMessages.Insert(0, currentObjectHeader); objectMessages.Add(string.Join(itemsSeparator, currentObjectMessages.ToArray()).TrimStart('\r', '\n')); } } return string.Join("\r\n", objectMessages.ToArray()); } RuleType GetRuleType(RuleSetValidationResultItem resultItem) { IModelValidationRules modelValidationRules = ((IModelApplicationValidation)application.Model).Validation.Rules; var modelRuleBaseWarning = ((IModelRuleBaseRuleType)modelValidationRules.OfType<ModelNode>().SingleOrDefault(node => node.Id == resultItem.Rule.Id)); if (modelRuleBaseWarning == null) return RuleType.Critical; return modelRuleBaseWarning.RuleType; } string GetErrorPrefix(RuleType ruleType) { if (ruleType == RuleType.Critical) return ""; return CaptionHelper.GetDisplayText(ruleType) + ": "; } } internal class ObjectDisplayTextComparer : IComparer { public int Compare(object x, object y) { return string.Compare(ReflectionHelper.GetObjectDisplayText(x), ReflectionHelper.GetObjectDisplayText(y)); } } } public class ActionExecuteContextTargetObjectSelector : ValidationTargetObjectSelector { protected override bool NeedToValidateObject(IObjectSpace objectSpace, object targetObject) { return true; } } }

    PermissionValidationController.cs

    C#
    using System.Security; using DevExpress.ExpressApp; using DevExpress.ExpressApp.Validation; using DevExpress.Persistent.Base.Security; namespace SampleProject.Module.Infrastructure.ConfirmableValidationRules { public class PermissionValidationController : ViewController { private PersistenceValidationController persistenceValidationController; public PermissionValidationController() { TargetObjectType = typeof(IPersistentPermission); TargetViewType = ViewType.DetailView; } private void controller_ContextValidating(object sender, ContextValidatingEventArgs e) { IPermission permission = ((IPersistentPermission)View.CurrentObject).Permission; if (!e.TargetObjects.Contains(permission)) e.TargetObjects.Add(permission); } protected override void OnActivated() { base.OnActivated(); persistenceValidationController = Frame.GetController<PersistenceValidationController>(); if (persistenceValidationController != null) persistenceValidationController.ContextValidating += controller_ContextValidating; } protected override void OnDeactivated() { base.OnDeactivated(); if (persistenceValidationController != null) { persistenceValidationController.ContextValidating -= controller_ContextValidating; persistenceValidationController = null; } } } }

    RuleErrorTypeAttribute.cs

    C#
    using System; namespace SampleProject.Module.Infrastructure.ConfirmableValidationRules { [AttributeUsage(AttributeTargets.Property | AttributeTargets.Class, AllowMultiple = true)] public class RuleErrorTypeAttribute : Attribute { readonly string _id; readonly RuleType _ruleType; public RuleErrorTypeAttribute(string id, RuleType ruleType) { _id = id; _ruleType = ruleType; } public RuleType RuleType { get { return _ruleType; } } public string Id { get { return _id; } } } }

    RuleType.cs

    C#
    using DevExpress.Persistent.Base; using DevExpress.Xpo; namespace SampleProject.Module.Infrastructure.ConfirmableValidationRules { public enum RuleType { Critical, [DisplayName("Warning")] [ImageName("DevExpress.XtraEditors.Images.Warning")] Warning, [DisplayName("Information")] [ImageName("DevExpress.XtraEditors.Images.Information")] Information } }

    RuleTypeController.cs

    C#
    using System; using System.Collections.Generic; using System.ComponentModel; using System.Linq; using DevExpress.ExpressApp; using DevExpress.ExpressApp.Editors; using DevExpress.ExpressApp.Model; using DevExpress.ExpressApp.Model.Core; using DevExpress.ExpressApp.Utils; using DevExpress.ExpressApp.Validation; using DevExpress.Persistent.Validation; namespace SampleProject.Module.Infrastructure.ConfirmableValidationRules { public interface IModelRuleBaseRuleType : IModelNode { [Category("eXpand")] RuleType RuleType { get; set; } } public abstract class RuleTypeController : ViewController<ObjectView>, IModelExtender { public const string ObjectSpaceObjectChanged = "ObjectSpaceObjectChanged"; protected Dictionary<RuleType, IEnumerable<RuleSetValidationResultItem>> Dictionary = new Dictionary<RuleType, IEnumerable<RuleSetValidationResultItem>>(); protected override void OnActivated() { base.OnActivated(); Validator.RuleSet.ValidationCompleted += RuleSetOnValidationCompleted; if (HasNonCriticalRulesForControlValueChangedContext()) { ObjectSpace.ObjectChanged += ObjectSpaceOnObjectChanged; } } void ObjectSpaceOnObjectChanged(object sender, ObjectChangedEventArgs objectChangedEventArgs) { if (!string.IsNullOrEmpty(objectChangedEventArgs.PropertyName)) { ValidateControlValueChangedContext(objectChangedEventArgs.Object); } } protected bool HasNonCriticalRulesForControlValueChangedContext() { return ((IModelApplicationValidation)Application.Model).Validation.Rules.OfType<IModelRuleBaseRuleType>().FirstOrDefault(IsTypeInfoCriticalRuleForControlValueChangedContext()) != null; } Func<IModelRuleBaseRuleType, bool> IsTypeInfoCriticalRuleForControlValueChangedContext() { return type => type.RuleType != RuleType.Critical && ((IRuleBaseProperties)type).TargetType == View.ObjectTypeInfo.Type && ((IRuleBaseProperties)type).TargetContextIDs.Contains(ObjectSpaceObjectChanged); } protected void ValidateControlValueChangedContext(object currentObject) { Validator.RuleSet.ValidateAll(new List<object> { currentObject }, ObjectSpaceObjectChanged); } protected override void OnDeactivated() { base.OnDeactivated(); if (Validator.RuleSet != null) Validator.RuleSet.ValidationCompleted -= RuleSetOnValidationCompleted; } protected virtual void RuleSetOnValidationCompleted(object sender, ValidationCompletedEventArgs validationCompletedEventArgs) { if (View == null || View.IsDisposed) return; // warunkowe reguły mają działać tylko w webie - w pozostałych środowiskach mają być traktowane jak zwykłe reguły. } protected bool CriticalErrorsNotExist(Dictionary<RuleType, List<RuleSetValidationResultItem>> items) { var item = items.FirstOrDefault(pair => pair.Key == RuleType.Critical); return item.Value==null || item.Value.Count == 0; } protected void Collect(IEnumerable<RuleSetValidationResultItem> resultItems, RuleType ruleType) { Dictionary[ruleType] = resultItems; if (View is DetailView) CollectPropertyEditors(resultItems, ruleType); } protected List<RuleSetValidationResultItem> GetResultsPerType(ValidationCompletedEventArgs validationCompletedEventArgs, RuleType ruleType) { return validationCompletedEventArgs.Exception.Result.Results.Where(item => item.State == ValidationState.Invalid && IsOfRuleType(item, ruleType)).ToList(); } protected virtual Dictionary<PropertyEditor, RuleType> CollectPropertyEditors(IEnumerable<RuleSetValidationResultItem> result, RuleType ruleType) { return result.SelectMany(GetPropertyEditors).Distinct().ToDictionary(propertyEditor => propertyEditor, propertyEditor => ruleType); } IEnumerable<PropertyEditor> GetPropertyEditors(RuleSetValidationResultItem resultItem) { return View.GetItems<PropertyEditor>().Where(editor => resultItem.Rule.UsedProperties.Contains(editor.MemberInfo.Name) && editor.Control != null); } bool IsOfRuleType(RuleSetValidationResultItem resultItem, RuleType ruleType) { IModelValidationRules modelValidationRules = ((IModelApplicationValidation)Application.Model).Validation.Rules; var modelRuleBaseWarning = ((IModelRuleBaseRuleType)modelValidationRules.OfType<ModelNode>().SingleOrDefault(node => node.Id == resultItem.Rule.Id)); return (modelRuleBaseWarning == null && ruleType == RuleType.Critical) || (modelRuleBaseWarning != null && modelRuleBaseWarning.RuleType == ruleType); } public void ExtendModelInterfaces(ModelInterfaceExtenders extenders) { extenders.Add<IModelRuleBase, IModelRuleBaseRuleType>(); } } }

    RuleTypeGeneratorUpdater.cs

    C#
    using System.Linq; using DevExpress.ExpressApp.Model; using DevExpress.ExpressApp.Model.Core; using DevExpress.ExpressApp.Validation; using DevExpress.Persistent.Base; using DevExpress.Persistent.Validation; namespace SampleProject.Module.Infrastructure.ConfirmableValidationRules { public class RuleTypeGeneratorUpdater : ModelNodesGeneratorUpdater<ModelValidationRulesNodeGenerator> { public override void UpdateNode(ModelNode node) { var modelValidationRules = ((IModelValidationRules)node).OfType<IRuleBaseProperties>(); foreach (var validationRule in modelValidationRules) { var modelRuleBaseWarning = (validationRule as IModelRuleBaseRuleType); if (modelRuleBaseWarning != null) modelRuleBaseWarning.RuleType = GetRuleType(validationRule); else { } } } RuleType GetRuleType(IRuleBaseProperties validationRule) { var modelClass = ((IModelNode)validationRule).Application.BOModel[validationRule.TargetType.FullName]; if (modelClass != null) { var ruleType = GetRuleType(modelClass, validationRule); return ruleType != RuleType.Critical ? ruleType : GetRuleType(validationRule as IRulePropertyValueProperties, modelClass); } return RuleType.Critical; } RuleType GetRuleType(IModelClass modelClass, IRuleBaseProperties validationRule) { var ruleErrorTypeAttribute = modelClass.TypeInfo.FindAttributes<RuleErrorTypeAttribute>().FirstOrDefault(attribute => attribute.Id == validationRule.Id); return ruleErrorTypeAttribute != null ? ruleErrorTypeAttribute.RuleType : RuleType.Critical; } RuleType GetRuleType(IRulePropertyValueProperties validationRule, IModelClass modelClass) { if (validationRule != null) { var modelMember = modelClass.FindMember(validationRule.TargetPropertyName); if (modelMember != null) { var ruleErrorTypeAttribute = modelMember.MemberInfo.FindAttributes<RuleErrorTypeAttribute>().FirstOrDefault(attribute => attribute.Id == validationRule.Id); if (ruleErrorTypeAttribute != null) { return ruleErrorTypeAttribute.RuleType; } } } return RuleType.Critical; } } }

    WebActionExecuteValidationController.cs

    C#
    using System; using System.Web.UI; using DevExpress.ExpressApp.Actions; using DevExpress.ExpressApp.Validation.AllContextsView; using DevExpress.ExpressApp.Web; using DevExpress.ExpressApp.Web.SystemModule; using DevExpress.ExpressApp.Web.Templates; using DevExpress.Persistent.Validation; using SampleProject.Module.Infrastructure.ConfirmableValidationRules; namespace SampleProject.Module.Web.Infrastructure.ConfirmableValidationRules { public class WebActionExecuteValidationController : ActionExecuteValidationController, IXafCallbackHandler { XafCallbackManager callbackManager; WebDetailViewController controller; protected override void OnActivated() { base.OnActivated(); controller = Frame.GetController<WebDetailViewController>(); if (controller != null) { controller.SaveAction.Executed -= ActionOnExecuted; controller.SaveAndCloseAction.Executed -= ActionOnExecuted; controller.SaveAndNewAction.Executed -= ActionOnExecuted; controller.SaveAction.Executing += ActionExecuting; controller.SaveAndCloseAction.Executing += ActionExecuting; controller.SaveAndNewAction.Executing += ActionExecuting; } } void ActionExecuting(object sender, System.ComponentModel.CancelEventArgs e) { if (View.ObjectTypeInfo.Type != typeof(ValidationResults)) { //if (View.ObjectTypeInfo.IsPersistent) if (View.ObjectSpace.ModifiedObjects.Count > 0) if (!Validator.RuleSet.ValidateAll(View.ObjectSpace.ModifiedObjects, "Save", CustomizeDeleteValidationException)) { e.Cancel = true; //WebWindow.CurrentRequestWindow.RegisterClientScript("ConfirmSave", "if(confirm('Are you sure to save? The rule is broken.')) { alert('Confirmed! Want to save.'); } else { alert('Cancelled! No saving...'); }"); WebWindow.CurrentRequestWindow.RegisterClientScript("ConfirmSave", "if(confirm('Are you sure you want to save? The are some warnings.')) {" + callbackManager.GetScript("callbackHandler", "'my_" + (sender as ActionBase).Id + "'", "", false) + "}"); } } } public void ProcessAction(string parameter) { if (parameter.StartsWith("my_Save")) { View.ObjectSpace.CommitChanges(); } if (controller != null) { if (parameter == "my_SaveAndClose") controller.SaveAndCloseAction.DoExecute(); else if (parameter == "my_SaveAndNew") controller.SaveAndNewAction.DoExecute(controller.SaveAndNewAction.SelectedItem); } } protected override void OnFrameAssigned() { base.OnFrameAssigned(); if (Frame != null) { if (Frame.Template != null) ((Page)Frame.Template).Load += new EventHandler(MyViewController_Load); else Frame.TemplateChanged += new EventHandler(Frame_TemplateChanged); } } void Frame_TemplateChanged(object sender, EventArgs e) { if (Frame.Template is Page) ((Page)Frame.Template).Load += new EventHandler(MyViewController_Load); } void MyViewController_Load(object sender, EventArgs e) { callbackManager = CallbackManager; callbackManager.RegisterHandler("callbackHandler", this); } private XafCallbackManager CallbackManager { get { XafCallbackManager manager = null; if (Frame != null && Frame.Template != null) { ICallbackManagerHolder holder = Frame.Template as ICallbackManagerHolder; if (holder != null) { manager = holder.CallbackManager; } } return manager; } } } }

    WebRuleTypeController.cs

    C#
    using System; using System.Collections.Generic; using DevExpress.ExpressApp.Editors; using DevExpress.ExpressApp.Utils; using DevExpress.ExpressApp.Web; using DevExpress.Persistent.Validation; using SampleProject.Module.Infrastructure.ConfirmableValidationRules; namespace SampleProject.Module.Web.Infrastructure.ConfirmableValidationRules { public class WebRuleTypeController : RuleTypeController { protected override Dictionary<PropertyEditor, RuleType> CollectPropertyEditors(IEnumerable<DevExpress.Persistent.Validation.RuleSetValidationResultItem> result, RuleType ruleType) { var dictionary = base.CollectPropertyEditors(result, ruleType); foreach (var keyValuePair in dictionary) { if (keyValuePair.Key.Control is TableEx) { EventHandler[] eventHandler = { null }; var pair = keyValuePair; eventHandler[0] = (sender, args) => { var tableEx = ((TableEx)sender); tableEx.PreRender -= eventHandler[0]; CreateRuleImage(pair, tableEx); }; ((TableEx)keyValuePair.Key.Control).PreRender += eventHandler[0]; } } return dictionary; } void CreateRuleImage(KeyValuePair<PropertyEditor, RuleType> keyValuePair, TableEx tableEx) { var tableCell = tableEx.Rows[0].Cells[0]; var image = new System.Web.UI.WebControls.Image(); var imageName = keyValuePair.Value.ToString(); ImageInfo imageInfo = ImageLoader.Instance.GetImageInfo(imageName); if (!imageInfo.IsEmpty) { image.AlternateText = imageName; image.ImageUrl = imageInfo.ImageUrl; image.Width = imageInfo.Width; image.Height = imageInfo.Height; image.ToolTip = keyValuePair.Key.ErrorMessage; image.Style["margin"] = "5px"; tableCell.Controls.Clear(); tableCell.Controls.Add(image); } } protected override void RuleSetOnValidationCompleted(object sender, ValidationCompletedEventArgs validationCompletedEventArgs) { if (View == null || View.IsDisposed) return; if (!validationCompletedEventArgs.Successful) { var items = new Dictionary<RuleType, List<RuleSetValidationResultItem>>(); foreach (RuleType ruleType in typeof(RuleType).GetEnumValues()) { var resultsPerType = GetResultsPerType(validationCompletedEventArgs, ruleType); items.Add(ruleType, resultsPerType); Collect(resultsPerType, ruleType); } validationCompletedEventArgs.Handled = CriticalErrorsNotExist(items); ErrorHandling.Instance.SetPageError(validationCompletedEventArgs.Exception); } } } }

    Module.cs

    C#
    public sealed partial class SampleProjectModule : ModuleBase { (...) public override void AddGeneratorUpdaters(ModelNodesGeneratorUpdaters updaters) { base.AddGeneratorUpdaters(updaters); updaters.Add(new RuleTypeGeneratorUpdater()); } public override void Setup(ApplicationModulesManager moduleManager) { base.Setup(moduleManager); var registrator = new ValidationRulesRegistrator(moduleManager); registrator.RegisterRule(typeof(Projekt.ProjektConditionRule), typeof(IRuleBaseProperties)); } }

    Remember also to embed images that are available in the attachment (the SampleProject.Module.Images folder).

      Show previous comments (23)
      Dennis Garavsky (DevExpress) 12 years ago

        Thanks, I know;-)
        You are always welcome!
        P.S.
        I apologize for the delayed answer anyway. I could debug the 12.2 source only today, while static analysis of the version 12.1 did not bring much results.

        KK KK
        Krzysztof Krzyzsłof 12 years ago

          ASPxDateEdit works fine. But since yesterday I've been trying to make ASPxListBox work with this functionality, and now I nearly give up.
          Could you please see the WebCheckedListBoxEnumPropertyEditor (my target functionality) and/or FromString_ListBoxPropertyEditor (a little simplified one) in my updated project?
          Both of them get cleared whenever you empty the 'Opis' field and click Save.
          I found such information: http://www.devexpress.com/Support/Center/p/Q303458.aspx
          Maybe it would solve the issue and be integrated in XAF somehow ?
          I am sorry to ask you one more time for the similar issue.

          Dennis Garavsky (DevExpress) 12 years ago

            Krzysztof, I think that your last problem deserves a separate thread, because otherwise, it will be difficult for other Support Center users to understand anything here.
            So, I have created a new ticket on your behalf: Custom PropertyEditor based on ASPxListBox loses values.

            Disclaimer: The information provided on DevExpress.com and affiliated web properties (including the DevExpress Support Center) is provided "as is" without warranty of any kind. Developer Express Inc disclaims all warranties, either express or implied, including the warranties of merchantability and fitness for a particular purpose. Please refer to the DevExpress.com Website Terms of Use for more information in this regard.

            Confidential Information: Developer Express Inc does not wish to receive, will not act to procure, nor will it solicit, confidential or proprietary materials and information from you through the DevExpress Support Center or its web properties. Any and all materials or information divulged during chats, email communications, online discussions, Support Center tickets, or made available to Developer Express Inc in any manner will be deemed NOT to be confidential by Developer Express Inc. Please refer to the DevExpress.com Website Terms of Use for more information in this regard.