Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
246 views
in Technique[技术] by (71.8m points)

c# - Is it possible to intercept the get method of a property when said property is marked with an attribute?

Preamble

I am using Selenium to run automated tests, and Autofac to manage the dependency injection at runtime.

Selenium uses a WebDriver to control the browser, usually by the interface IWebDriver. To find an element in a page, one can use the method FindElement which will return an object with the interface IWebElement. To find more than one element, we can use FindElements which in turn will return a ReadOnlyCollection<IWebElement>

The most common pattern used in web automation is Page Object Model, which establishes:

A page object is an object-oriented class that serves as an interface to a page of your AUT. The tests then use the methods of this page object class whenever they need to interact with the UI of that page. The benefit is that if the UI changes for the page, the tests themselves don’t need to change, only the code within the page object needs to change. Subsequently all changes to support that new UI are located in one place.

So an example of this could be:

public class SomePage
{
    private IWebDriver driver;
    private IWebElement UsernameElement => driver.FindElement(By.Id("username"));
    private IWebElement PasswordElement => driver.FindElement(By.Id("password"));
    private IWebElement LoginButton => driver.FindElement(By.Id("login"));

    public SomePage(IWebDriver driver)
    {
        this.driver = driver;
    }

    public Input_Username(string username) => UsernameElement.SendKeys(username);
    public Input_Password(string password) => PasswordElement.SendKeys(password);
    public Click_Login() => LoginButton.Click();
}

The problem

Recently I have been struggling a problem: if an element of a webpage is immutable (meaning once it has been set in the DOM, it will never change), I would like to save the value of that element the first time it is accessed and return it each subsequent time we need that element.

A first approach following with the previous example could be:

public class SomePage
{
    ...
    private IWebElement usernameBackingField;
    private IWebElement UsernameElement
    {
        get
        {
            if (usernameBackingField == null)
                usernameBackingField = driver.FindElement(By.Id("username"));
         
            return usernameBackingField;
        }
    }
    ...
}

This way, the first time we use the property UsernameElement, we save the value in usernameBackingField and each time we need this element we will have it saved.

If the element is particularly difficult to create, we can use Lazy<IWebElement> to save the value and later use Value:

public class SomePage
{
    ...
    private Lazy<IWebElement> lazyUsername;
    private IWebElement UsernameElement
    {
        get
        {
            if (lazyUsername == null || lazyUsername.IsValueCreated == false)
                lazyUsername = new Lazy<IWebElement>(() => driver.FindElement(By.Id("username")), LazyThreadSafetyMode.PublicationOnly);
         
            return lazyUsername.Value;
        }
    }
    ...
}

This example works as intended but it's a pain to do this for each element.

The question

Knowing what I need, that is: intercepting the get method of a Property, using it as a Func<IWebElement> for the Lazy<IWebElement> instantiation and later returning the Lazy.Value each time the property is accessed, is this something that Autofac could help me achieve so code like the following would work?

[Cacheable]
private IWebElement UsernameElement => driver.FindElement(By.Id("username"));

Thanks for your time.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

Autofac is a Dependecy injection tool, it means it will only helps getting instances and all its dependency graph. It won't help intercepting method or properties.

What you need is an Aspect Orient Programming tool. Such tools will help intercepting method and so on. One of the most popular one for .net is castle dynamic proxy

DynamicProxy will automatically inject proxy for your type and inject IInterceptor where needed.

Let's assume you have the following class

public class Test1
{
    [Cacheable]
    protected virtual String UserName => "test";
}

DynamicProxy will automatically create a class derived from Test1 and override the UserName properties to inject these IInterceptor. That the reason why the properties should be virtual.

Then, instead of working with Test1 you will work with a type given by dynamic proxy.

There is many way to get the proxy type, one of the solution is the following one :

ProxyGenerator generator = new ProxyGenerator();
IInterceptor interceptor = new CacheableInterceptor();

Test1 test = new Test1();
Test1 proxy = generator.CreateClassProxyWithTarget<Test1>(test, interceptor);

In this case each time you do something with proxy the CacheableInterceptor will be invoked.

A really simple implementation of CacheableInterceptor could be

public class CacheableInterceptor : IInterceptor
{
    private readonly Dictionary<String, String> _cache = new Dictionary<String, String>();

    public void Intercept(IInvocation invocation)
    {
        // This code is a sample and should not be used in production
        // performance optimization are available and is not thread safe

        var pi = invocation.Method
                           .DeclaringType
                           .GetProperties()
                           .Where(p => p.GetCustomAttributes<CacheableAttribute>().Any())
                           .FirstOrDefault(p => p.GetGetMethod() == invocation.Method);
        if (pi != null)
        {

            if (this._cache.TryGetValue(pi.Name, out var value))
            {
                invocation.ReturnValue = value;
            }
            else
            {
                invocation.Proceed();
                value = (String)invocation.ReturnValue;
                this._cache.Add(pi.Name, value);
            }
        }
    }
}

When you fully understand how dynamic proxy works. I would recommend to have a look at how Autofac works (without interception & so on) and then combine Autofac with DynamicProxy to make the magic happens : autofac can create the proxy, interceptor for you : See Autofac - Type interceptors


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...