در این مقاله، میخواهیم اهمیت options validation (اعتبارسنجی Options) و چند راه برای پیاده سازی آن در اپلیکیشنهای ASP.NET Core را یاد بگیریم. با وجود مکانیسمهای پیشرفته ای مانند بارگذاری مجدد پیکربندی (configuration reloading)، باید به دقت در مورد options validation فکر کنیم، زیرا نمی خواهیم باعث رخداد خرابی برنامه یا رفتار غیرمنتظره ای در آن شویم.
فقط جهت یادآوری، در مقاله قبلی، در مورد options interface ها و نحوه پیاده سازی آنها صحبت کردیم. حالا باید از اپلیکیشنمان، در مقابل مقادیر نامعتبر پیکربندی محافظت کنیم.
چه ما روی یک پروژه بزرگ با کلی پارامترهای پیکربندی کار کنیم و چه متکی به configuration provider های خارجی باشیم، اعتبارسنجی، نقش مهمی را در چرخه عمر (lifecycle) اپلیکیشن ایفا میکند.
اگر میخواهید در طول مسیر این مقاله، ما را دنبال کنید، از نقطه شروع پروژه استفاده کنید. برای بررسی پروژه تکمیل شده این مقاله، از پروژه نهایی این مقاله استفاده کنید.
در این مقاله، میخواهیم در مورد عناوین زیر صحبت کنیم:
- اعتبارسنجی Options با DataAnnotation ها
- اعتبارسنجی Options با استفاده از Delegate ها
- سناریوهای پیچیده اعتبارسنجی با IValidateOptions
- نتیجه گیری
اعتبارسنجی Options با DataAnnotation ها
یک شیوه ی اعتبارسنجی برای پارامترهای پیکربندی، استفاده از DataAnnotation از System.ComponentModel.DataAnnotations میباشد.
ممکن است که شما data annotation های مورد استفاده را در دیگر سناریوها مانند اعتبارسنجی فرمها در Asp.net Core MVC یا در Blazor دیده باشید.
اما ما میتوانیم از آنها، برای اعتبارسنجی configuration خود در شروع اپلیکیشن یا در بارگذاری مجدد configuration استفاده کنیم.
حالا اجازه بدید تا تعدادی data annotation را در مدل TitleConfiguration خود اضافه کنیم:
1 2 3 4 5 6 7 8 9 |
public class TitleConfiguration { [Required] [MaxLength(60)] public string WelcomeMessage { get; set; } public bool ShowWelcomeMessage { get; set; } public string Color { get; set; } public bool UseRandomTitleColor { get; set; } } |
ما دو attribute به نام [Required]
و [MaxLength]
را اضافه کردیم، بنابراین میتوانیم تعیین کنیم که ویژگی WelcomeMessage ما از نوع اجباری است و نباید بیش از 60 کاراکتر باشد.
حال که ویژگی WelcomeMessage خود را با این دو attribute مزین کردیم، باید validator خود را برای بررسی کردن این مقادیر، پیکربندی کنیم.
در مقاله قبلی، ما options حود را در کلاس Startup پیکربندی کرده بودیم:
1 |
services.Configure<TitleConfiguration>("HomePage", Configuration.GetSection("Pages:HomePage")); |
حال به دلیل فعال کردن ویژگی اعتبارسنجی پیکربندی، میتوانیم این خط را حذف کنیم و کمی آن را تغییر دهیم:
1 2 3 |
services.AddOptions<TitleConfiguration>() .Bind(Configuration.GetSection("Pages:HomePage")) .ValidateDataAnnotations(); |
اینجا ما از متد ()AddOptions برای افزودن configuration و از متد ()Bind برای bind کردن پیکربندی به یک subsection خاص در پیکربندی استفاده میکنیم که در این مثال ما، “Pages:HomePage” میباشد. سپس میتوانیم متد ()ValidateDataAnnotations را برای اطمینان از اینکه، validation ما برای data annotation هایی که اعمال کرده ایم، trigger میشود را فراخوانی کنیم.
ما همچنین می توانیم TitleService ، ITitleService و HomeController را به سرعت به حالت قبل برگردانیم تا از named option استفاده نکنیم، زیرا دیگر نیازی به آنها نداریم:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
public class TitleColorService : ITitleColorService { private readonly string[] _colors = { "red", "green", "blue", "black", "purple", "yellow", "brown", "pink" }; private readonly TitleConfiguration _titleConfiguration; public TitleColorService(IOptionsMonitor<TitleConfiguration> titleConfiguration) { _titleConfiguration = titleConfiguration.CurrentValue; } public string GetTitleColor() { var random = new Random(); var colorIndex = random.Next(7); return _titleConfiguration.UseRandomTitleColor ? _colors[colorIndex] : _titleConfiguration.Color; } } |
1 2 3 4 |
public interface ITitleColorService { string GetTitleColor(); } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
public HomeController(ILogger<HomeController> logger, IOptionsSnapshot<TitleConfiguration> homePageTitleConfiguration, ITitleColorService titleColorService) { _logger = logger; _homePageTitleConfiguration = homePageTitleConfiguration.Value; _titleColorService = titleColorService; } public IActionResult Index() { var homeModel = new HomeModel { Configuration = _homePageTitleConfiguration }; homeModel.Configuration.Color = _titleColorService.GetTitleColor(); return View(homeModel); } |
حال به appsettings.json برمیگردیم و گزینه WelcomeMessage را حذف میکنیم (ما همچنین می توانیم سکشن “ProductPage” را نیز حذف کنیم زیرا نیازی به آن نخواهیم داشت):
1 2 3 4 5 6 7 |
"Pages": { "HomePage": { "ShowWelcomeMessage": true, "Color": "black", "UseRandomTitleColor": true } }, |
قطعا اگر حالا برنامه را اجرا کنیم، OptionsValidationException را دریافت میکنیم:

علاوه بر این، جزیاات فیلدی که این مشکل را برای ما ایجاد کرد را نیز اینجا میتوانیم ببینیم که در این مثال ما، آن فیلد WelcomeMessage میباشد.
همچنین میتوانیم با افزودن چند کلمه بیشتر به گزینه WelcomeMessage، نوع اعتبارسنجی MaxLength را نیز تست کنیم:
1 2 3 4 5 6 7 8 |
"Pages": { "HomePage": { "WelcomeMessage": "Hi human, and welcome to the ProjectConfigurationDemo Home Page", "ShowWelcomeMessage": true, "Color": "black", "UseRandomTitleColor": true } }, |
حالا اینجا، exception با مضمون طول بیش از حد maximum را دریافت میکنیم:

عالی، همه چیز به درستی کار میکند.
و نه تنها این، حتی اگر در هنگام اجرای برنامه، پیکربندی را تغییر هم دهیم و آن را اصلاح کنیم، نیز همچنان کار می کند. آن را امتحان کنید و به WelcomeMessage، یک مقدار معتبر بدهید و صفحه را refresh کنید تا ببینید چه انفاقی می افتد.
نیازی به گفتن نیست. آن خارق العاده است.
اما اگر به logic اعتبارسنجی پیچیده تری نیاز داشته باشیم، تکلیف چیست؟
اعتبارسنجی Options با استفاده از Delegate ها
یک شیوه عالی دیگر برای تنظیم options validation، استفاده از delegate ها میباشد. سریعترین راه برای انجام این کار، استفاده از یک anonymous function داخل متد ()Validate است که یک extension از OptionsBuilder که قبلا استفاده کرده بودیم میباشد:
1 2 3 4 5 6 7 8 9 10 |
services.AddOptions<TitleConfiguration>() .Bind(Configuration.GetSection("Pages:HomePage")) .ValidateDataAnnotations() .Validate(config => { if (string.IsNullOrEmpty(config.WelcomeMessage) || config.WelcomeMessage.Length > 60) return false; return true; }); |
حالا میتوانیم ()ValidateDataAnnotations را از اینجا و خود data annotation ها را از کلاس TitleConfiguration حذف کنیم. همانطور که میبینید، این شیوه خیلی پیچیده تر است و میتوانیم هرچیز سفارشی که میخواهیم را اینجا انجام دهیم.
حالا اگر WelcomeMessage را کامنت کنیم و مجددا برنامه را اجرا کنیم، اینبار یک exception با یک تفاوت کوچک را دریافت میکنیم:

این پیغام، کمی کلی است، اما از آنجایی که ما خودمان داریم این logic اعتبارسنجی سفارشی را طراحی میکنیم، انتظار میرود که بتوان نوع پیغام را تغییر داد. ما میتوانیم با تعریف یک پیغام fail، آن را کمی بهتر کنیم:
1 2 3 4 5 6 7 8 9 |
services.AddOptions<TitleConfiguration>() .Bind(Configuration.GetSection("Pages:HomePage")) .Validate(config => { if (string.IsNullOrEmpty(config.WelcomeMessage) || config.WelcomeMessage.Length > 60) return false; return true; }, "Welcome message must be defined and it must be less than 60 characters long."); |
حالا exception ما باید این پیغام را نشان دهد:

البته ما میتوانیم کارهای خیلی بهتری را نیز با delegate ها انجام دهیم. بنابراین اگر با delegate ها آشنا نیستید، مقاله ما در مورد delegate ها در #C را بررسی کنید.
عالی، ما یاد گرفتیم که چطور در صورت نیاز، اعتبارسنجی سفارشی خود را انجام دهیم. با این متدها، ما قادریم validation را به سرعت پیاده سازی کنیم.
اما بیاید ببینیم که در سناریوهای واقعی پیچیده اعتبارسنجی، چه کاری میتوانیم انجام دهیم.
سناریوهای اعتبارسنجی پیچیده با استفاده از IValidateOptions
برای سناریوهای اعتبارسنجی پیچیده تر، که چندان نادر هم نیستند، می توانیم از اینترفیس IValidateOptions استفاده کنیم تا منطق اعتبار سنجی خود را از کلاس Startup خارج کرده و آن را به کلاس جداگانه خودش منتقل کنیم.
برای این منظور، اول یک کلاس به نام TitleConfigurationValidation ایجاد میکنیم و اینترفیس IValidateOptions را در آن پیاده سازی میکنیم. برای این هدف، میتوانیم یک پوشه جداگانه به نام ConfigurationValidation ایجاد کنیم و سپس یک کلاس جدید به نام TitleConfigurationValidation درون آن بسازیم:
1 2 3 4 5 6 7 8 9 |
public class TitleConfigurationValidation : IValidateOptions<TitleConfiguration> { private readonly TitleConfiguration _titleConfiguration; public ValidateOptionsResult Validate(string name, TitleConfiguration options) { throw new NotImplementedException(); } } |
ما میخواهیم اینترفیس IValidateOptions را با استفاده از TitleConfiguration به عنوان پارامتر options برای متد ()Validate پیاده سازی کنیم.
این اینترفیس IValidateOptions فقط یک متد ()Validate را تعریف میکند. و این متد، دو آرگومان name و options میگیرد. همچنین میبینیم که این متد، ValidateOptionsResult را به عنوان نتیجه برمیگرداند که یک روش مناسب برای ارائه اطلاعات نتیجه است. بسیار مناسب تر از این است که مانند قبل فقط true یا false را بخواهیم به عنوان نتیجه برگردانیم.
اول میتوانیم options validation که پیشتر در کلاس Startup انجام داده بودیم را به این متد انتقال دهیم:
1 2 3 4 5 6 7 |
public ValidateOptionsResult Validate(string name, TitleConfiguration options) { if (string.IsNullOrEmpty(options.WelcomeMessage) || options.WelcomeMessage.Length > 60) return ValidateOptionsResult.Fail("Welcome message must be defined and it must be less than 60 characters long."); return ValidateOptionsResult.Success; } |
این قطعا خیلی بهتر است. حالا میتوانیم کلاس Startup را تمیز و مرتب کنیم و TitleConfigurationValidation خود را به عنوان یک singleton ثبت کنیم:
1 2 3 4 5 6 7 8 9 10 |
public void ConfigureServices(IServiceCollection services) { services.Configure<TitleConfiguration>(Configuration.GetSection("Pages:HomePage")); services.TryAddSingleton<IValidateOptions<TitleConfiguration>, TitleConfigurationValidation>(); services.TryAddSingleton<ITitleColorService, TitleColorService>(); services.AddControllersWithViews(); } |
اگر برنامه را مجددا اجرا کنیم، همان نتیجه مشابه قبل را دریافت میکنیم.
حال اجازه دهید تا با پیاده سازی اعتبار سنجی برای رنگ عنوان، تمام قدرت اینترفیس IValidateOptions را به نمایش بگذاریم. برای مثال بگوییم که می خواهیم مطمئن شویم که رنگ عنوان فقط از بین رنگ هایی است که ما ارائه می دهیم تا پایداری و سازگاری در برنامه ما باقی بماند.
ما فقط باید کلاس Validation خود را برای پشتیبانی از این منطق، توسعه دهیم:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
public class TitleConfigurationValidation : IValidateOptions<TitleConfiguration> { private readonly string[] _colors = { "red", "green", "blue", "black", "purple", "yellow", "brown", "pink" }; public ValidateOptionsResult Validate(string name, TitleConfiguration options) { if (string.IsNullOrEmpty(options.WelcomeMessage) || options.WelcomeMessage.Length > 60) return ValidateOptionsResult.Fail("Welcome message must be defined and it must be less than 60 characters long."); if (!_colors.Any(c => c == options.Color)) return ValidateOptionsResult.Fail($"Provided title color '{options.Color}' is not among allowed colors."); return ValidateOptionsResult.Success; } } |
حالا با این حساب، رنگ مورد نظر باید از بین این رنگهایی که ارائه داده ایم باشد. اگر رنگ متفاوتی را در نظر بگیریم، به عنوان مثال، رنگ gray:
1 2 3 4 5 6 7 8 |
"Pages": { "HomePage": { "WelcomeMessage": "Welcome to the ProjectConfigurationDemo Home Page", "ShowWelcomeMessage": true, "Color": "gray", "UseRandomTitleColor": true } }, |
حال بعد از اجرای برنامه، این پیغام را دریافت میکنیم که میگوید که “gray” یک رنگ معتبر نیست:

اما حتی بهتر از این هم می شود، این رویکرد از بارگذاری مجدد پیکربندی نیز پشتیبانی می کند، بنابراین می توانید رنگ را به “black” برگردانید و صفحه را refresh کنید تا مجددا کار کند.
عالی! ما یاد گرفتیم که چطور configuration خود را با چند راه مختلف، اعتبارسنجی کنیم.
نتیجه گیری
در این مقاله، ما اعتبارسنجی options را به 3 راه مختلف یاد گرفتیم. اولین راه، استفاده از DataAnnotation ها بود که یک راه رایج برای اعتبارسنجی فیلدها در ASP.NET Core است. ما همچنین نحوه پیکربندی options را با استفاده از delegate ها نیز دیدیم و در آخر، یاد گرفتیم که چطور از IValidateOptions برای اعتبارسنجی سناریوهای پیچیده استفاده کنیم و کد خود را تمیز و مرتب کنیم.
در قسمت بعدی، ما در مورد configuration provider های مختلف بیشتر یاد میگیریم و میتوانید دیگر قسمتهای این سری از آموزشهای سریالی را در صفحه Asp.net Core Web Api پیدا کنید.