New Posts New Posts RSS Feed: My Async Verifier implementation
  FAQ FAQ  Forum Search   Calendar   Register Register  Login Login

My Async Verifier implementation

 Post Reply Post Reply
Author
ting View Drop Down
IdeaBlade
IdeaBlade
Avatar

Joined: 27-Mar-2009
Location: San Francisco
Posts: 427
Post Options Post Options   Quote ting Quote  Post ReplyReply Direct Link To This Post Topic: My Async Verifier implementation
    Posted: 06-Oct-2011 at 12:48pm
Glad everything is working! Thanks for the update.

Back to Top
katit View Drop Down
Senior Member
Senior Member


Joined: 09-Sep-2011
Posts: 146
Post Options Post Options   Quote katit Quote  Post ReplyReply Direct Link To This Post Posted: 05-Oct-2011 at 7:56pm
Verification of removed/deleted entities was false alarm. In fact, I was executing verification process manually from my code!
Originally posted by ting

Are you seeing validation being performed on entities whose fields have never been modified?
 
False alarm as well. I was running verification manually before save and that's why I saw it. Now everything works great.
 
I consider my mission accomplished for now :)
 
Now i need to figure out different validation levels. Like Error, Warning, etc.


Edited by katit - 05-Oct-2011 at 8:01pm
Back to Top
ting View Drop Down
IdeaBlade
IdeaBlade
Avatar

Joined: 27-Mar-2009
Location: San Francisco
Posts: 427
Post Options Post Options   Quote ting Quote  Post ReplyReply Direct Link To This Post Posted: 05-Oct-2011 at 5:28pm
1.  Yes, just look at the entity aspect and cut short validation if the entity is scheduled for deletion. I have updated a feature request to skip validation of entities scheduled for deletion.

2.  DevForce only saves entities that are marked as modified. It's possible to have an "unchanged" entity in the modified state if you modify the fields and then set them back to their original values. DevForce doesn't check for this because it's more performance intensive. Are you seeing validation being performed on entities whose fields have never been modified?

Back to Top
katit View Drop Down
Senior Member
Senior Member


Joined: 09-Sep-2011
Posts: 146
Post Options Post Options   Quote katit Quote  Post ReplyReply Direct Link To This Post Posted: 03-Oct-2011 at 7:11pm
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 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 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 :)
 
 


Edited by katit - 03-Oct-2011 at 7:21pm
Back to Top
ting View Drop Down
IdeaBlade
IdeaBlade
Avatar

Joined: 27-Mar-2009
Location: San Francisco
Posts: 427
Post Options Post Options   Quote ting Quote  Post ReplyReply Direct Link To This Post Posted: 03-Oct-2011 at 5:39pm
Hi katit,

That's a pretty reasonable conversion if you want to do asynchronous validation through the verification engine. A few comments:

1)  Instead of testing .Count() != 0 or .Count() > 0, you can use .Any(). In addition to being shorter, it is also faster.

2)  I'm tempted to break out the device id verification logic into it's own class and call that class from the UniqueIdVerifier. That separation seems to be a bit cleaner, and if you happen to have multiple instances of the UniqueIdVerifier running, they will all share the same cache.

Good job and thanks for sharing!

Back to Top
katit View Drop Down
Senior Member
Senior Member


Joined: 09-Sep-2011
Posts: 146
Post Options Post Options   Quote katit Quote  Post ReplyReply Direct Link To This Post Posted: 29-Sep-2011 at 3:06pm
Didn't find anything in documentation - I'm still trying to work with DevForce Verification engine instead of doing my own as I think it's pretty good already.
 
So, here we go. This is my "UniqueIdVerifier". I didn't make it generic yet, my application have artificial ID's with surrogate keys (identity). So, there is a need to check ID all of the time when user creates record. This is client-side verifier, I won't implement one on server side, also I don't see a problem doing it. In my case if duplicate makes to server - I will get constraint exception back to client and it's OK by me. For more "business rule" validation type I would duplicate it on server as well.
 
Couple thing about my verifier:
1. I cache all submitted values so I verify them just once. This way if user types value and it is verified once - user can always type it again and verifier will not hit database second time
2. VerificationManager want's response NOW. So, I always answer "OK" - which means verification succeeded.
3. After actual Async operation completed - I go back to VerificationManager and say "Hey, now call me again". Now, when verification manager calls my verifier - I know about same value and I have status for it so I can answer OK or Error.
4. My verifier implements IAsyncVerifier (my interface) - all it does - provides IsVerifierRunning property. This way when I save my entity - I can always iterate Verifiers and check if there any running ones left. This is handy to pospone save until verification completed.
 
Is there any suggestions for improvement? #3 is my concern since I'm not sure of VerificationManager internals. Seems to be working great, but maybe I can do it better?

public class UniqueIdVerifier : PropertyValueVerifier, IAsyncVerifier
    {
        private const string ErrorMessage = "Device with this Id already exists";

        private readonly List<KeyValuePair<string, AsyncVerifyStatus>> testedValues = new List<KeyValuePair<string, AsyncVerifyStatus>>();
       
        public UniqueIdVerifier(PropertyValueVerifierArgs args)
            : base(args)
        {
        }

        public bool IsVerifierRunning()
        {
            return this.testedValues.Where(x => x.Value == AsyncVerifyStatus.Running).Count() != 0;
        }

        protected override VerifierResult VerifyValue(object itemToVerify, object valueToVerify, TriggerContext triggerContext, VerifierContext verifierContext)
        {
            // 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.Key.Equals(valueToVerify.ToString())).Count() != 0)
            {
                return this.testedValues.Where(x => x.Key.Equals(valueToVerify.ToString())).First().Value == AsyncVerifyStatus.Error
                    ? new VerifierResult(false, ErrorMessage)
                    : new VerifierResult(true);
            }

            // Start Async check by adding value with status to our collection
            var testedValue = new KeyValuePair<string, AsyncVerifyStatus>(valueToVerify.ToString(), AsyncVerifyStatus.Running);
            this.testedValues.Add(testedValue);

            var device = itemToVerify as MBLDevice;
            var manager = device.EntityAspect.EntityManager as IDATTApplicationEntities;

            var query = manager.MBLDevices.Where(d => !d.DeviceKey.Equals(device.DeviceKey) && d.DeviceId.Equals(valueToVerify.ToString()));
            query.ExecuteAsync(
                op =>
                    {
                        this.testedValues.Remove(testedValue);

                        IEnumerable<MBLDevice> devices = op.Results;

                        if (op.HasError)
                        {
                            op.IsErrorHandled = true;
                            testedValue = new KeyValuePair<string, AsyncVerifyStatus>(testedValue.Key, AsyncVerifyStatus.Error);
                        }

                        testedValue = devices.Count() > 0
                            ? new KeyValuePair<string, AsyncVerifyStatus>(testedValue.Key, AsyncVerifyStatus.Error)
                            : new KeyValuePair<string, AsyncVerifyStatus>(testedValue.Key, AsyncVerifyStatus.Ok);

                        this.testedValues.Add(testedValue);

                        // Execute engine again. Careful - can cause infinite loop?!
                        verifierContext.VerifierEngine.Execute(itemToVerify, new List<Verifier> { this }, verifierContext);
                    });
           
            return new VerifierResult(true);
        }
    }



Edited by katit - 29-Sep-2011 at 3:07pm
Back to Top
 Post Reply Post Reply

Forum Jump Forum Permissions View Drop Down