|
I changed to "Any", thanks for suggestion. As far as #2 - yes, I found that "issue". Now my cache keeps object along with value so I don't hit wrong cache item.I made some changes, converted verifier to use Generics and pass-through eSQL so now it is doing async data lookup and verifies against specific value. I placed it on server side but didn't implement code to run on server. If ever need to - I can add that functionality easily.. Issues I have now: 1. DevForce does verification against deleted entities, so I just cut it off on my verifier. 2. DevForce does verification on Save even though there was no change to property. This behavior is not desired when doing Async checks and I need to figure out how to prevent this. My idea of Async verifier is that it should be completed before save happen.. This is new code: namespace IDATT.Model.Verifiers { using System.Collections.Generic; using System.Linq; using IdeaBlade.Core; using IdeaBlade.EntityModel; using IdeaBlade.Validation; public class QueryDataAsyncVerifier<TObj, TResult> : PropertyValueVerifier, IAsyncVerifier { private readonly List<AsyncVerifierCacheItem<TObj, string>> testedValues = new List<AsyncVerifierCacheItem<TObj, string>>(); public QueryDataAsyncVerifier(PropertyValueVerifierArgs args, PassthruEsqlQuery query, TResult expectedResult, string errorMessage) : base(args) { this.Query = query; this.ExpectedResult = expectedResult; this.ErrorMessage = errorMessage; } private PassthruEsqlQuery Query { get; set; } private string ErrorMessage { get; set; } private TResult ExpectedResult { get; set; } public bool IsVerifierRunning() { return this.testedValues.Where(x => x.IsRunning).Count() != 0; } protected override VerifierResult VerifyValue(object itemToVerify, object valueToVerify, TriggerContext triggerContext, VerifierContext verifierContext) { // On server - just say OK. TODO: Same check on server? if (IdeaBladeApplication.IsExecutingOnServer) return new VerifierResult(true); var entity = itemToVerify as Entity; var manager = entity.EntityAspect.EntityManager as IDATTApplicationEntities; // We will validate only new or edited entities. if (!(entity.EntityAspect.EntityState == EntityState.Added || entity.EntityAspect.EntityState == EntityState.Modified)) { return new VerifierResult(true); } if (this.Query == null) { return new VerifierResult(false, "QueryDataAsyncVerifier wasn't properly initialized."); } // If we already checked this value or if we in process of validating - handle this here // There is no need to execute Async process if (this.testedValues.Where(x => x.TestedObject.Equals(itemToVerify) && x.TestedValue.Equals(valueToVerify)).Any()) { return this.testedValues.Where(x => x.TestedObject.Equals(itemToVerify) && x.TestedValue.Equals(valueToVerify)).First().IsError ? new VerifierResult(false, this.ErrorMessage) : new VerifierResult(true); } // Start Async check by adding value with status to our collection var testedValue = new AsyncVerifierCacheItem<TObj, string> { Status = AsyncVerifyStatus.Running, TestedObject = (TObj)itemToVerify, TestedValue = valueToVerify as string }; this.testedValues.Add(testedValue); // Sometime query will rely on parameters that need to be populated from entity properties foreach (var parameter in this.Query.ParameterizedEsql.Parameters) { parameter.Value = entity.EntityAspect.GetDataProperty(parameter.Name).GetValue(entity); } this.Query.ParameterizedEsql.Parameters.First().Value = valueToVerify; var query = this.Query.With(manager); query.ExecuteAsync( op => { if (op.HasError) { op.IsErrorHandled = true; testedValue.Status = AsyncVerifyStatus.Error; } else { testedValue.Status = op.Results.Cast<TResult>().First().Equals(this.ExpectedResult) ? AsyncVerifyStatus.Ok : AsyncVerifyStatus.Error; } // Execute engine again. Careful - can cause infinite loop?! verifierContext.VerifierEngine.Execute(itemToVerify, new List<Verifier> { this }, verifierContext); }); return new VerifierResult(true); } } }
And here is how I initialize it: public IEnumerable<Verifier> GetVerifiers(object verifierProviderContext) { var uniqueIdArgs = new PropertyValueVerifierArgs( typeof(MBLDevice), PropertyMetadata.DeviceId.Name, false, "DeviceId"); var checkIdEsql = new ParameterizedEsql("SELECT VALUE Count(0) FROM MBLDevices AS d WHERE mailto:d.DeviceId=@DeviceId - d.DeviceId=@DeviceId AND d.DeviceKey<>@DeviceKey", new QueryParameter("DeviceId", null), new QueryParameter("DeviceKey", null)); var checkIdQuery = new PassthruEsqlQuery(typeof(int), typeof(MBLDevice), checkIdEsql); var serialCheckArgs = new PropertyValueVerifierArgs( typeof(MBLDevice), PropertyMetadata.SerialNumber.Name, false, "SerialNumberDisplayName"); var checkSerialEsql = new ParameterizedEsql("SELECT VALUE Count(0) FROM MBLDevices AS d WHERE mailto:d.SerialNumber=@SerialNumber - d.SerialNumber=@SerialNumber AND d.DeviceKey<>@DeviceKey", new QueryParameter("SerialNumber", null), new QueryParameter("DeviceKey", null)); var checkSerialQuery = new PassthruEsqlQuery(typeof(int), typeof(MBLDevice), checkSerialEsql); var verifiers = new List<Verifier> { new QueryDataAsyncVerifier<MBLDevice, int>(uniqueIdArgs, checkIdQuery, 0, "Device with same Id already exist"), new QueryDataAsyncVerifier<MBLDevice, int>(serialCheckArgs, checkSerialQuery, 0, "Device with same serial number already exist") }; return verifiers; } Before entity saved on UI - I do sanity check (make sure Async operation completed) // Check all async verifiers to make sure they done running foreach (var verifier in this.CurrentItem.EntityAspect.VerifierEngine.GetVerifiers()) { if (verifier.GetType().GetInterface(typeof(IAsyncVerifier).FullName, true) == null) { continue; } if ((verifier as IAsyncVerifier).IsVerifierRunning()) { this.notificationFromViewModelInteractionRequest.Raise(new Notification { Content = "Record still being validated. Please try again in a moment.", Title = "title" }, dummy => { }); return; } So, right now if I edit record and verifier wasn't executed on property in question - it will have to run before save and my message pops up. But I don't really need to execute this verifier. That is my #2 concern. I almost need to change "behavior" of my verifier to run only when property change happen and NOT when record being saved. IdeaBlade! If you included IAsyncVerifier into your API (plus handle this interface during save) - all developers can create async running verifiers like this and let your engine handle before save checks.. Of cource there would be some "assumptions" and rules of us developers to follow when we come up with custom async verifiers but DevBlade can take some of the "trickery" away :)
|