C#:Как создать атрибут для метода, запускающего событие при его вызове?
-
03-07-2019 - |
Вопрос
Есть ли способ в C # или .NET в целом создать атрибут для метода, который запускает событие при вызове метода?В идеале я мог бы запускать пользовательские действия до и после вызова метода.
Я имею в виду что-то вроде этого:
[TriggersMyCustomAction()]
public void DoSomeStuff()
{
}
Я совершенно не представляю, как это сделать и возможно ли это вообще, но Система.Диагностика.Условный атрибут мог бы проделать нечто подобное в фоновом режиме.Хотя я не уверен.
Редактировать:Я забыл упомянуть, что в силу обстоятельств моего конкретного случая производительность на самом деле не является проблемой.
Решение
Единственный способ, которым я знаю, как это сделать, - это PostSharp . Он обрабатывает ваш IL и может делать то, что вы просили.
Другие советы
Эта концепция используется в MVC веб-приложения.
Тот Самый .NET Framework 4.x предоставляет несколько атрибутов, которые запускают действия, например: ExceptionFilterAttribute
(обработка исключений), AuthorizeAttribute
(разрешение на обработку).Оба они определены в System.Web.Http.Filters
.
Вы могли бы, например, определить свой собственный атрибут авторизации следующим образом:
public class myAuthorizationAttribute : AuthorizeAttribute
{
protected override bool IsAuthorized(HttpActionContext actionContext)
{
// do any stuff here
// it will be invoked when the decorated method is called
if (CheckAuthorization(actionContext))
return true; // authorized
else
return false; // not authorized
}
}
Затем, в вашем контроллер класс, в котором вы описываете методы, которые должны использовать вашу авторизацию, следующим образом:
[myAuthorization]
public HttpResponseMessage Post(string id)
{
// ... your code goes here
response = new HttpResponseMessage(HttpStatusCode.OK); // return OK status
return response;
}
Всякий раз, когда Post
будет вызван метод, он вызовет IsAuthorized
метод внутри myAuthorization
Атрибут до того, как код внутри Post
метод выполнен.
Если ты вернешься false
в IsAuthorized
метода, вы сигнализируете о том, что авторизация не предоставлена, и выполнение метода прекращается. Post
прерывается.
Чтобы понять, как это работает, давайте рассмотрим другой пример:Тот Самый ExceptionFilter
, который позволяет фильтровать исключения с помощью атрибутов, использование аналогично показанному выше для AuthorizeAttribute
(вы можете найти более подробное описание его использования здесь).
Чтобы использовать его, выведите DivideByZeroExceptionFilter
класс из ExceptionFilterAttribute
как показано на рисунке здесь, и переопределите метод OnException
:
public class DivideByZeroExceptionFilter : ExceptionFilterAttribute
{
public override void OnException(HttpActionExecutedContext actionExecutedContext)
{
if (actionExecutedContext.Exception is DivideByZeroException)
{
actionExecutedContext.Response = new HttpResponseMessage() {
Content = new StringContent("An error occured within the application.",
System.Text.Encoding.UTF8, "text/plain"),
StatusCode = System.Net.HttpStatusCode.InternalServerError
};
}
}
}
Затем используйте следующий демонстрационный код, чтобы запустить его:
[DivideByZeroExceptionFilter]
public void Delete(int id)
{
// causes the DivideByZeroExceptionFilter attribute to be triggered:
throw new DivideByZeroException();
}
Теперь, когда мы знаем, как это используется, нас в основном интересует реализация.Следующий код взят из .NET Framework.Он использует интерфейс IExceptionFilter
внутренне в качестве контракта:
namespace System.Web.Http.Filters
{
public interface IExceptionFilter : IFilter
{
// Executes an asynchronous exception filter.
// Returns: An asynchronous exception filter.
Task ExecuteExceptionFilterAsync(
HttpActionExecutedContext actionExecutedContext,
CancellationToken cancellationToken);
}
}
Тот Самый ExceptionFilterAttribute
сам по себе определяется следующим образом:
namespace System.Web.Http.Filters
{
// Represents the attributes for the exception filter.
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method,
Inherited = true, AllowMultiple = true)]
public abstract class ExceptionFilterAttribute : FilterAttribute,
IExceptionFilter, IFilter
{
// Raises the exception event.
// actionExecutedContext: The context for the action.</param>
public virtual void OnException(
HttpActionExecutedContext actionExecutedContext)
{
}
// Asynchronously executes the exception filter.
// Returns: The result of the execution.
Task IExceptionFilter.ExecuteExceptionFilterAsync(
HttpActionExecutedContext actionExecutedContext,
CancellationToken cancellationToken)
{
if (actionExecutedContext == null)
{
throw Error.ArgumentNull("actionExecutedContext");
}
this.OnException(actionExecutedContext);
return TaskHelpers.Completed();
}
}
}
Внутри ExecuteExceptionFilterAsync
, способ OnException
называется.Поскольку вы переопределили его, как показано ранее, ошибка теперь может быть обработана вашим собственным кодом.
Существует также доступный коммерческий продукт, как упомянуто в ответе OwenP, Постшарповый, что позволяет вам сделать это легко. Здесь вот пример того, как вы можете сделать это с помощью PostSharp.Обратите внимание, что доступна экспресс-версия, которую вы можете использовать бесплатно даже для коммерческих проектов.
Пример PostSharp (смотрите ссылку выше для получения полного описания):
public class CustomerService
{
[RetryOnException(MaxRetries = 5)]
public void Save(Customer customer)
{
// Database or web-service call.
}
}
Здесь атрибут указывает, что Save
метод вызывается до 5 раз, если возникает исключение.Следующий код определяет этот пользовательский атрибут:
[PSerializable]
public class RetryOnExceptionAttribute : MethodInterceptionAspect
{
public RetryOnExceptionAttribute()
{
this.MaxRetries = 3;
}
public int MaxRetries { get; set; }
public override void OnInvoke(MethodInterceptionArgs args)
{
int retriesCounter = 0;
while (true)
{
try
{
args.Proceed();
return;
}
catch (Exception e)
{
retriesCounter++;
if (retriesCounter > this.MaxRetries) throw;
Console.WriteLine(
"Exception during attempt {0} of calling method {1}.{2}: {3}",
retriesCounter, args.Method.DeclaringType, args.Method.Name, e.Message);
}
}
}
}
Вам нужен какой-то аспектно-ориентированный фреймворк. PostSharp сделает это, как и Windsor .
По сути, они наследуют ваш объект и переопределяют этот метод ...
тогда это становится:
//proxy
public override void DoSomeStuff()
{
if(MethodHasTriggerAttribute)
Trigger();
_innerClass.DoSomeStuff();
}
Конечно, все это скрыто от вас. Все, что вам нужно сделать, это спросить Виндзор о типе, и он сделает прокси для вас. Атрибут становится (пользовательским) средством, которое я думаю в Виндзоре.
Вы можете использовать ContextBoundObject и IMessageSink. См. http://msdn.microsoft.com/nb- нет / журнал / cc301356 (EN-US) .aspx
Имейте в виду, что этот подход сильно влияет на производительность по сравнению с прямым вызовом метода.
Я не думаю, что есть способ сделать это только с помощью атрибута, но используя прокси-классы и отражение у вас может быть класс, который знает, что он перехватывает экземпляры классов, которым вы приписали методы. Р>
Затем прокси-класс может инициировать событие при каждом вызове атрибутивных методов.
Атрибут дает информацию, это метаданные. Я не знаю, как это сделать, кто-то может.
Вы можете посмотреть на частичные методы в .NET, которые позволяют вам выполнять некоторую облегченную обработку событий. Вы предоставляете хуки и позволяете кому-то другому заниматься реализацией. Если метод не реализован, компилятор просто игнорирует его.
Вы могли бы взглянуть на решение бедняка: см. образец декоратора.