حقن التبعية مع السمات على .NET API

· 2 دقيقة قراءة

من المحتمل أن يكون حقن التبعية أحد أفضل الميزات المتوفرة لدينا على .NET في هذه المرحلة. لا توجد طريقة في أي حالة محتملة لعدم استخدامها، لذلك إذا كنت مثلي، فأنت تريد إضافتها إلى جميع عمليات التنفيذ التي تقوم بها.

المرشحات، وفقًا لـ [وثائق] Microsoft الرسمية(https://learn.microsoft.com/en-us/aspnet/core/mvc/controllers/filters?view=aspnetcore-3.1):

تسمح عوامل التصفية في ASP.NET Core بتشغيل التعليمات البرمجية قبل أو بعد مراحل محددة في مسار معالجة الطلب.

تتعامل المرشحات المضمنة مع مهام مثل:

  • التفويض، منع الوصول إلى الموارد غير المصرح للمستخدم بها.

التخزين المؤقت للاستجابة، مما يؤدي إلى تقصير مسار الطلب لإرجاع استجابة مخبأة.

يمكن إنشاء مرشحات مخصصة للتعامل مع الاهتمامات الشاملة. تتضمن أمثلة الاهتمامات الشاملة معالجة الأخطاء والتخزين المؤقت والتكوين والترخيص والتسجيل. تتجنب المرشحات تكرار التعليمات البرمجية.

أنا أعمل كثيرًا مع واجهات برمجة التطبيقات (APIs) وهناك بعض الأشياء التي يجب أن تقوم بتشغيل كل طلب على حدة، أو جميعها تقريبًا، لذا، من الأفضل أن ما نريد القيام به هو العمل معها بالإضافة إلى…. حقن التبعية!

لكنها خدعة بعض الشيء في بعض الأحيان، فهي لا تعمل كما نريد إذا أردنا أن نرث من ActionAttribute لذلك نحتاج إلى العمل مع TypeFilterAttribute، مما يتيح لنا القيام بالأشياء عند تجاوز OnActionExecutionAsync.

عادةً ما أقوم بإنشاء هذه المرشحات لإجراء بعض التسجيل، لذلك سنستخدم ذلك كمثال:

/// <summary>
/// LoggedQueryAttribute class
/// </summary>
public class LoggedQueryTypeFilterAttribute : TypeFilterAttribute
{
    /// <summary>
    /// Constructor for <see cref="LoggedQueryTypeFilterAttribute"/>
    /// </summary>
    public LoggedQueryTypeFilterAttribute() : base(typeof(LoggedQueryFilter))
    {
    }

    /// <summary>
    /// LoggedQueryFilter class
    /// </summary>
    private class LoggedQueryFilter : IAsyncActionFilter
    {
        /// <summary>
        /// <see cref="_loggingService"/> object
        /// </summary>
        private readonly LoggingService _loggingService;

        /// <summary>
        /// Constructor for <see cref="LoggedQueryFilter"/>
        /// </summary>
        /// <param cref="LoggingService" name="loggingService">Parameter for loggingService</param>
        public LoggedQueryFilter(LoggingService loggingService)
        {
            _loggingService = loggingService;
        }

        /// <summary>
        /// OnActionExecutionAsync
        /// </summary>
        /// <param cref="ActionExecutingContext" name="context">Parameter for context</param>
        /// <param cref="ActionExecutionDelegate" name="next">Parameter for next</param>
        public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
        {
            // Get properties
            var properties = (Request)context.ActionArguments.First().Value!;

            // Get call from context
            var call = context.HttpContext.Request.Path.Value!;

            // Logging
            _loggingService.LogCustomEvent(call);

            // Continue call
            await next();
        }
    }
}

المنطق بسيط جدًا، نحصل على النص عن طريق الوصول إلى كائن context باستخدام context.ActionArguments.First().Value، كما نستخدم استدعاء الأسلوب باستخدام context.HttpContext.Request.Path.Value.

ثم نقوم فقط باستدعاء طريقتنا من خدمتنا، في هذه الحالة هي _loggingService.LogCustomEvent(call).

بعد ذلك، يجب علينا الاتصال بـ await next();، لأن خط الأنابيب يجب أن يستمر.

هذا بالنسبة للسمة، والآن، يجب علينا بالفعل تضمين هذه السمة في إحدى الطرق.

[LoggedQueryTypeFilterAttribute]
public ActionResult<string> TestFilter()
{
    return Ok("Hello world!");
}

آمل أن تكون قد نالت إعجابك، إذا كان لديك أي أسئلة أو كنت ترغب في التواصل معي، فلا تتردد واتصل بي!