نظرات مطالب
مقایسه امنیت Oracle11g و SQL server 2008 از دید آمار در سال 2009
کم شدن باگ‌های امنیتی مایکروسافت در چند سال اخیر سه دلیل عمده داشته:
- استفاده بیشتر از دات نت فریم ورک در محصولات عمده خودش (اس کیوال سرور، exchange server، SharePoint و ...). برای مثال دات نت فریم ورک به حملات سرریز بافر (اگر کد خالص دات نتی باشد و از API ویندوز به صورت ناشیانه استفاده نکرده باشد) مقاوم است و خیلی مسایل دیگر که به صورت توکار در دات نت لحاظ شده.
- وضع کردن یک سری قوانین سفت و سخت در مورد کتابخانه‌ها و توابع C مورد استفاده در محصولات نوشته شده با سی و CPP
https://www.dntips.ir/2009/05/bannedh.html
- تهیه برنامه‌هایی که کار آنالیز خودکار فایل‌های باینری و همچنین سورس‌ کدهای سازمان آن‌را انجام می‌دهند. به این صورت قبل از اینکه دیگران از سیستم‌های آن‌ها باگ‌های متداول را درآورند، خودشان نسبت به اینکار اقدام می‌کنند.
http://blogs.msdn.com/bharry/archive/2009/10/02/two-free-security-tools-from-microsoft-sdl-team.aspx
https://www.dntips.ir/2009/11/blog-post.html
مسیرراه‌ها
Entity framework code-first
شروع به کار با EF Code first

برای تکمیل بحث نیاز است تغییرات انجام شده از نگارش 4 به 6 را نیز مد نظر داشته باشید:


آشنایی با مباحث Migrations



آشنایی با تنظیمات نگاشت‌ها به دو روش استفاده از ویژگی‌ها و Fluent API



اعتبارسنجی و بررسی استثناءها



ردیابی تغییرات



استفاده از SQL خام و بانک‌های اطلاعاتی متفاوت

      نکات مهم کوئری نویسی در EF



      استفاده از EF در WPF


      لایه بندی پروژه‌های EF Code first



      پروژ‌ه‌های انجام شده با EF Code first

       
      نظرات اشتراک‌ها
      راهنمای مایکروسافت در مورد مقابله با SQL Injection
      ممنون؛ تو پروژه‌های دیگه از linq  استفاده کرده‌ایم حتی تو این پروژه در جاهایی از کوئری پارامتریک استفاده شده بنا به نیاز نمی‌خوایم وقتی برای این پروژه بگزاریم چون قدیمی است می‌خواستم بدونم چقدر تاثیر گذاره این روش ؟
      یک گروهی قصد سو استفاده دارند و تونستن وارد دیتابیس بشن می‌خواستیم جلوشونو بگیریم.
      مطالب
      خلاصه‌ای از آغاز به کار با NHibernate

      اگر شش یا هفت قسمت قبل را بخواهیم به صورت سریع مرور کنیم می‌توان به ویدیوی زیر مراجعه کرد:


      در طی یک ربع، خیلی سریع به دریافت فایل‌های لازم، ایجاد یک پروژه جدید، افزودن ارجاعات لازم، استفاده از fluent NHibernate برای ساخت نگاشت‌ها و سپس استفاده از LINQ to NHibernate برای کوئری گرفتن از اطلاعات دیتابیس اشاره کرده است (که از این لحاظ کاملا به روز است).


      پاسخ به بازخورد‌های پروژه‌ها
      موقع ویرایش کاربر خطا داره
      دلیل آنکه متد GetPagedList به خوبی کار میکرد. این بود که بنده فعلا نادیده گرفته  بودم فیلد‌های DateTime را
      ، مشکل را در کامنت فبلی گفتم خدمتتون و ربطی به متد‌های Async ندارد. پروایدر Linq توانایی ترجمه این نوع کوئری را ندارد.
      تغییرات جدید را اعمال کردم (باز هم با متد‌های Async)
      نتیجه:

      مطالب
      خواندنی‌های 16 تیر

      اس کیوال سرور

      توسعه وب

      دات نت فریم ورک

      دبلیو پی اف و سیلور لایت

      سی و مشتقات

      شیرپوینت

      کتاب‌های رایگان

      مای اس کیوال

      متفرقه

      وب سرورها

      پی اچ پی

      مطالب
      لینک‌های هفته آخر آبان

      وبلاگ‌ها و سایت‌های ایرانی


      Visual Studio


      ASP. Net


      طراحی وب


      اس‌کیوال سرور


      به روز رسانی‌ها


      سی‌شارپ


      عمومی دات نت


      PHP


      ویندوز


      گوگل


      متفرقه




      مطالب دوره‌ها
      خلاصه‌ای از اعمال متداول با AutoMapper و Entity Framework
      فرض کنید کلاس‌های مدل برنامه از سه کلاس مشتری، سفارشات مشتری‌ها و اقلام هر سفارش تشکیل شده‌است:
      public class Customer
      {
          public int Id { set; get; }
          public string FirstName { get; set; }
          public string LastName { get; set; }
          public string Bio { get; set; }
       
          public virtual ICollection<Order> Orders { get; set; }
       
          [Computed]
          [NotMapped]
          public string FullName
          {
              get { return FirstName + ' ' + LastName; }
          }
      }
      
      public class Order
      {
          public int Id { set; get; }
          public string OrderNo { get; set; }
          public DateTime PurchaseDate { get; set; }
          public bool ShipToHomeAddress { get; set; }
       
          public virtual ICollection<OrderItem> OrderItems { get; set; }
       
          [ForeignKey("CustomerId")]
          public virtual Customer Customer { get; set; }
          public int CustomerId { get; set; }
       
          [Computed]
          [NotMapped]
          public decimal Total
          {
              get { return OrderItems.Sum(x => x.TotalPrice); }
          }
      }
      
      public class OrderItem
      {
          public int Id { get; set; }
          public decimal Price { get; set; }
          public string Name { get; set; }
          public int Quantity { get; set; }
       
          [ForeignKey("OrderId")]
          public virtual Order Order { get; set; }
          public int OrderId { get; set; }
       
          [Computed]
          [NotMapped]
          public decimal TotalPrice
          {
              get { return Price * Quantity; }
          }
      }
      در اینجا برای پیاده سازی خواص محاسباتی، از نکته‌ی مطرح شده‌ی در مطلب «نگاشت خواص محاسبه شده به کمک AutoMapper و DelegateDecompiler» استفاده شده‌است.
      در ادامه می‌خواهیم اطلاعات حاصل از این کلاس‌ها را با شرایط خاصی به ViewModelهای مشخصی جهت نمایش در برنامه نگاشت کنیم.


      نمایش اطلاعات مشتری‌ها

      می‌خواهیم اطلاعات مشتری‌ها را مطابق فرمت کلاس ذیل بازگشت دهیم:
      public class CustomerViewModel
      {
          public string Bio { get; set; }
          public string CustomerName { get; set; }
      }
      با این شرایط که
      - اگر Bio نال بود، بجای آن N/A نمایش داده شود.
      - CustomerName از خاصیت محاسباتی FullName کلاس مشتری تامین گردد.

      برای حل این مساله، نیاز است نگاشت زیر را تهیه کنیم:
      this.CreateMap<Customer, CustomerViewModel>()
         .ForMember(dest => dest.CustomerName, opt => opt.MapFrom(entity => entity.FullName))
         .ForMember(dest => dest.Bio, opt => opt.MapFrom(entity => entity.Bio ?? "N/A"));
      AutoMapper برای جایگزین کردن خواص با مقدار نال، با یک مقدار مشخص، از متدی به نام NullSubstitute استفاده می‌کند. اما در این حالت خاص که قصد داریم از Project To استفاده کنیم، این روش پاسخ نمی‌دهد و محدودیت‌هایی دارد. به همین جهت از روش map from و بررسی مقدار خاصیت، استفاده شده‌است.
      همچنین در اینجا مطابق نگاشت فوق، خاصیت CustomerName از خاصیت FullName کلاس مشتری دریافت می‌شود.

      کوئری نهایی استفاده کننده‌ی از این اطلاعات به شکل زیر خواهد بود:
      using (var context = new MyContext())
      {
          var viewCustomers = context.Customers
              .Project()
              .To<CustomerViewModel>()
              .Decompile()
              .ToList();
          // don't use
          // var viewCustomers = Mapper.Map<IEnumerable<Customer>, IEnumerable<CustomerViewModel>>(dbCustomers);
          foreach (var customer in viewCustomers)
          {
              Console.WriteLine("{0} - {1}", customer.CustomerName, customer.Bio);
          }
      }
      در اینجا از متدهای Project To و همچنین Decompile استفاده شده‌است (جهت پردازش خاصیت محاسباتی).


      نمایش اطلاعات سفارش‌های مشتری‌ها

      در ادامه قصد داریم اطلاعات سفارش‌ها را با فرمت ViewModel ذیل نمایش دهیم:
      public class OrderViewModel
      {
          public string CustomerName { get; set; }
          public decimal Total { get; set; }
          public string OrderNumber { get; set; }
          public IEnumerable<OrderItemsViewModel> OrderItems { get; set; }
      }
      
      public class OrderItemsViewModel
      {
          public string Name { get; set; }
          public int Quantity { get; set; }
          public decimal Price { get; set; }
      }
      با این شرایط که
      - CustomerName از خاصیت محاسباتی FullName کلاس مشتری تامین گردد.
      - خاصیت OrderNumber آن از خاصیت OrderNo تهیه گردد.

      به همین جهت کار را با تهیه‌ی نگاشت ذیل ادامه می‌دهیم:
      this.CreateMap<Order, OrderViewModel>()
        .ForMember(dest => dest.OrderNumber, opt => opt.MapFrom(src => src.OrderNo))
        .ForMember(dest => dest.CustomerName, opt => opt.MapFrom(src => src.Customer.FullName));
      بر این اساس کوئری مورد استفاده نیز به نحو ذیل تشکیل می‌شود:
      using (var context = new MyContext())
      {
          var viewOrders = context.Orders
              .Project()
              .To<OrderViewModel>()
              .Decompile()
              .ToList();
          // don't use
          // var viewOrders = Mapper.Map<IEnumerable<Order>, IEnumerable<OrderViewModel>>(dbOrders);
          foreach (var order in viewOrders)
          {
              Console.WriteLine("{0} - {1} - {2}", order.OrderNumber, order.CustomerName, order.Total);
          }
      }
      در اینجا چون از خاصیت OrderItems کلاس ViewModel صرفنظر نشده‌است، اطلاعات آن نیز به همراه viewOrders موجود است. یعنی می‌توان چنین کوئری را نیز جهت نمایش اطلاعات تو در توی اقلام هر سفارش نیز نوشت:
      using (var context = new MyContext())
      {
          var viewOrders = context.Orders
              .Project()
              .To<OrderViewModel>()
              .Decompile()
              .ToList();
          // don't use
          // var viewOrders = Mapper.Map<IEnumerable<Order>, IEnumerable<OrderViewModel>>(dbOrders);
          foreach (var order in viewOrders)
          {
              Console.WriteLine("{0} - {1} - {2}", order.OrderNumber, order.CustomerName, order.Total);
              foreach (var item in order.OrderItems)
              {
                  Console.WriteLine("({0}) {1} - {2}", item.Quantity, item.Name, item.Price);
              }
          }
      }
      اگر می‌خواهید OrderItems به صورت خودکار واکشی نشود، نیاز است در نگاشت تهیه شده، توسط متد Ignore از آن صرفنظر کنید:
      this.CreateMap<Order, OrderViewModel>()
        .ForMember(dest => dest.OrderNumber, opt => opt.MapFrom(src => src.OrderNo))
        .ForMember(dest => dest.OrderItems, opt => opt.Ignore())
        .ForMember(dest => dest.CustomerName, opt => opt.MapFrom(src => src.Customer.FullName));


      نمایش اطلاعات یک سفارش، با فرمتی خاص

      تا اینجا نگاشت‌های انجام شده بر روی لیستی از اشیاء صورت گرفتند. در ادامه می‌خواهیم اولین سفارش ثبت شده را با فرمت ذیل نمایش دهیم:
      public class OrderDateViewModel
      {
          public int PurchaseHour { get; set; }
          public int PurchaseMinute { get; set; }
          public string CustomerName { get; set; }
      }
      به همین منظور ابتدا نگاشت ذیل را تهیه می‌کنیم:
      this.CreateMap<Order, OrderDateViewModel>()
        .ForMember(dest => dest.PurchaseHour, opt => opt.MapFrom(src => src.PurchaseDate.Hour))
        .ForMember(dest => dest.PurchaseMinute, opt => opt.MapFrom(src => src.PurchaseDate.Minute))
        .ForMember(dest => dest.CustomerName, opt => opt.MapFrom(src => src.Customer.FullName));
      در اینجا ساعت و دقیقه‌ی خرید، از خاصیت PurchaseDate استخراج شده‌اند. همچنین CustomerName نیز از خاصیت FullName کلاس مشتری دریافت گردیده‌است.
      پس از این تنظیمات، کوئری نهایی به شکل ذیل خواهد بود:
      using (var context = new MyContext())
      {
          var viewOrder = context.Orders
              .Project()
              .To<OrderDateViewModel>()
              .Decompile()
              .FirstOrDefault();
          // don't use
          // var viewOrder = Mapper.Map<Order, OrderDateViewModel>(dbOrder);
       
          if (viewOrder != null)
          {
              Console.WriteLine("{0}, {1}:{2}", viewOrder.CustomerName, viewOrder.PurchaseHour, viewOrder.PurchaseMinute);
          }
      }


      فرمت کردن سفارشی اطلاعات در حین نگاشت‌ها

      در مورد فرمت کننده‌های سفارشی و تبدیلگرها پیشتر بحث کرده‌ایم. اما اغلب آن‌ها را در حالت خاص LINQ to Entities نمی‌توان بکار برد، زیرا قابلیت تبدیل به SQL را ندارند. برای مثال فرض کنید می‌خواهیم خاصیت ShipToHomeAddress کلاس Order را به خاصیت ShipHome کلاس ذیل نگاشت کنیم:
      public class OrderShipViewModel
      {
          public string ShipHome { get; set; }
          public string CustomerName { get; set; }
      }
      با این شرط که اگر مقدار آن True بود، Yes را نمایش دهد. با توجه به ساختار مدنظر، نگاشت ذیل را می‌توان تهیه کرد که در آن فرمت کردن سفارشی، به متد MapFrom واگذار شده‌است:
      this.CreateMap<Order, OrderShipViewModel>()
         .ForMember(dest => dest.ShipHome, opt => opt.MapFrom(src=>src.ShipToHomeAddress? "Yes": "No"))
         .ForMember(dest => dest.CustomerName, opt => opt.MapFrom(src => src.Customer.FullName));
      با این کوئری جهت استفاده‌ی از این تنظیمات:
      using (var context = new MyContext())
      {
          var viewOrders = context.Orders
              .Project()
              .To<OrderShipViewModel>()
              .Decompile()
              .ToList();
          // don't use
          // var viewOrders = Mapper.Map<IEnumerable<Order>, IEnumerable<OrderShipViewModel>>(dbOrders);
          foreach (var order in viewOrders)
          {
              Console.WriteLine("{0} - {1}", order.CustomerName, order.ShipHome);
          }
      }

      کدهای کامل این مطلب را از اینجا می‌توانید دریافت کنید.
      نظرات مطالب
      روش استفاده‌ی صحیح از HttpClient در برنامه‌های دات نت
      سلام. من به این شکل و طبق الگو، پیاده سازی کردم
      private async Task<(Token Token, Dictionary<string, string> AppCookies)> LoginAsync(string requestUri, string username, string password)
              {
                  var viewmodel = new LoginViewModel { Username = username, Password = password };
                  var host = new Uri("http://localhost:5000");
                  var httpClient = _httpClientFactoryService.GetOrCreate(host);
                  var responseMessage = await httpClient.PostAsJsonAsync("api/account/login", viewmodel).ConfigureAwait(false);
                  var responseContent = await responseMessage.Content.ReadAsStringAsync().ConfigureAwait(false);
                 // return Content(responseContent);
      ...
      }
      اما این خطا رو میده:
      No connection could be made because the target machine actively refused it
      تو استک هم سرچ کردم میگه از فایر وال و آنتی ویروسه که جفتش هم خاموشه. چه باید بکنم؟
      مطالب
      آزمایش ساده‌تر Web APIs توسط strest
      در سری کار با Postman، یک روش بسیار متداول آزمایش Web APIs را بررسی کردیم. اما ... برای کار آن با مدام نیاز است از این برگه به آن برگه مراجعه کرد و ارتباط دادن درخواست‌های متوالی در آن مشکل است. به همین منظور تابحال راه‌حل‌های زیادی برای جایگزین کردن postman ارائه شده‌اند که یکی از آن‌ها strest است. این ابزار خط فرمان:
      - بسیار سبک ورزن است و تنها نیاز به نصب بسته‌ی npm آن‌را دارد.
      - با فایل‌های متنی معمولی کار می‌کند که ویرایش و copy/paste در آن‌ها بسیار ساده‌است.
      - قرار دادن فایل‌های نهایی متنی آن در ورژن کنترل بسیار ساده‌است.
      - امکان نوشتن درخواست‌های به هم وابسته و آزمودن نتایج حاصل را دارا است.
      - چون یک ابزار خط فرمان است، امکان استفاده‌ی از آن به سادگی در فرآینده‌های توسعه‌ی مداوم وجود دارد.
      - ابزارهای npm، چندسکویی هستند.


      نصب strest

      در ادامه قصد داریم مطلب «آزمایش Web APIs توسط Postman - قسمت ششم - اعتبارسنجی مبتنی بر JWT» را با استفاده از strest بازنویسی کنیم. به همین جهت در ابتدا نیاز است بسته‌ی npm آن‌را به صورت سراسری نصب کنیم:
      npm i -g @strest/cli
      پس از آن فایل جدید JWT.strest.yml را در پوشه‌ای ایجاد کرده و آن‌را تکمیل می‌کنیم. برای اجرای فرامین موجود در آن تنها کافی است دستور strest JWT.strest.yml را درخط فرمان صادر کنیم.


      مرحله 1: خاموش کردن بررسی مجوز SSL برنامه
      مرحله 2: ایجاد درخواست login و دریافت توکن‌ها

      مجوز SSL آزمایشی برنامه‌ی ASP.NET Core ما، از نوع خود امضاء شده‌است. به همین جهت اگر سعی در اجرای strest را با درخواست‌های ارسالی به آن داشته باشیم، باشکست مواجه خواهند شد. بنابراین در ابتدا، خاصیت allowInsecure را به true تنظیم می‌کنیم:
      version: 2
      
      variables:
        baseUrl: https://localhost:5001/api
        logResponse: false
      
      allowInsecure: true
      - این تنظیمات با فرمت yaml نوشته می‌شوند. به همین جهت در اینجا تعداد spaceها مهم است.
      - همچنین در ابتدای این تنظیمات، روش تعریف متغیرها را نیز مشاهده می‌کنید که برای مثال توسط آن‌ها baseUrl تعریف شده‌است.
      درست در سطر پس از این تنظیمات، دستور اجرا و اعتبارسنجی درخواست Login را می‌نویسیم:
      requests:
        loginRequest:
          request:
            url: <$ baseUrl $>/account/login
            method: POST
            postData:
              mimeType: application/json
              text:
                username: "Vahid"
                password: "1234"
          log: <$ logResponse $>
          validate:
            - jsonpath: content.access_token
              type: [string]
            - jsonpath: content.refresh_token
              type: [string]
      توضیحات:
      - درخواست‌ها با requests شروع می‌شوند. سپس ذیل آن می‌توان نام چندین درخواست یا request را ذکر کرد که برای مثال نام درخواست تعریف شده‌ی در اینجا loginRequest است. این نام مهم است؛ از این جهت که با اشاره‌ی به آن می‌توان به فیلدهای خروجی response حاصل، در درخواست‌های بعدی، دسترسی یافت.
      - سپس، آدرس درخواست مشخص شده‌است. در اینجا روش کار با متغیرها را نیز مشاهده می‌کنید.
      - نوع درخواست POST است.
      - در ادامه جزئیات اطلاعات ارسالی به سمت سرور باید مشخص شوند. برای مثال در اینجا با فرمت application/json قرار است یک شیء تشکیل شده‌ی از username و password ارسال شوند.
      - در سطر بعدی، خاصیت log با متغیر logResponse مقدار دهی شده‌است. اگر به true تنظیم شود، اصل خروجی response را توسط برنامه‌ی خط فرمان strest می‌توان مشاهده کرد. اگر اینکار خروجی را شلوغ کرد، می‌توان آن‌را به false تنظیم کرد و این خروجی را در فایل strest_history.json نهایی که حاصل از اجرای آزمایش‌های تعریف شده‌است، در کنار فایل JWT.strest.yml خود یافت و مشاهده کرد.
      - سپس به قسمت آزمودن نتیجه‌ی درخواست می‌رسیم. در اینجا انتظار داریم که درخواست حاصل که با فرمت json است، دارای دو خاصیت رشته‌ای access_token و refresh_token باشد.


       مرحله‌ی 3: ذخیره سازی توکن‌های دریافتی در متغیرهای سراسری
       مرحله‌ی 3: ذخیره سازی مراحل انجام شده
      در حین کار با strest نیازی به ذخیره سازی نتیجه‌ی حاصل از response، در متغیرهای خاصی نیست. برای مثال اگر بخواهیم به نتیجه‌ی حاصل از عملیات لاگین فوق در درخواست‌های بعدی دسترسی پیدا کنیم، می‌توان نوشت <$ loginRequest.content.access_token $>
      در اینجا درج متغیرها توسط <$ $> صورت می‌گیرد. سپس loginRequest به نام درخواست مرتبط اشاره می‌کند. خاصیت content.access_token نیز مقدار خاصیت access_token شیء response را بر می‌گرداند.

      همچنین ذخیره سازی مراحل انجام شده نیز نکته‌ی خاصی را به همراه ندارد. یک تک فایل متنی JWT.strest.yml وجود دارد که آزمایش‌های ما در آن درج می‌شوند.


      مرحله‌ی 4: دسترسی به منابع محافظت شده‌ی سمت سرور

      در ادامه روش تعریف دو درخواست جدید دیگر را در فایل JWT.strest.yml مشاهده می‌کنید که از نوع Get هستند و به اکشن متدهای محافظت شده ارسال می‌شوند:
        myProtectedApiRequest:
          request:
            url: <$ baseUrl $>/MyProtectedApi
            method: GET
            headers:
              - name: Authorization
                value: Bearer <$ loginRequest.content.access_token $>
          log: <$ logResponse $>
          validate:
            - jsonpath: content.title
              expect: "Hello from My Protected Controller! [Authorize]"
      
        mProtectedAdminApiRequest:
          request:
            url: <$ baseUrl $>/MyProtectedAdminApi
            method: GET
            headers:
              - name: Authorization
                value: Bearer <$ loginRequest.content.access_token $>
          log: <$ logResponse $>
          validate:
            - jsonpath: content.title
              expect: "Hello from My Protected Admin Api Controller! [Authorize(Policy = CustomRoles.Admin)]"
      دو نکته‌ی جدید در اینجا قابل مشاهده‌است:
      - چون نیاز است به همراه درخواست خود، هدر اعتبارسنجی مبتنی بر JWT را که به صورت Bearer value است نیز به سمت سرور ارسال کنیم، خاصیت headers را توسط یک name/value مشخص کرده‌ایم. همانطور که عنوان شد در فایل‌های yaml، فاصله‌ها و تو رفتگی‌ها مهم هستند و حتما باید رعایت شوند.
      - سپس دومین آزمون نوشته شده را نیز مشاهده می‌کنید. در قسمت validate، مشخص کرده‌ایم که خاصیت title دریافتی از response باید مساوی مقدار خاصی باشد.

      دقیقا همین نکات برای درخواست دوم به MyProtectedAdminApi تکرار شده‌اند.


      مرحله‌ی 5: ارسال Refresh token و دریافت یک سری توکن جدید

      اکشن متد account/RefreshToken در سمت سرور، نیاز دارد تا یک شیء جی‌سون با خاصیت refreshToken را دریافت کند. مقدار این خاصیت از طریق response متناظر با درخواست نام‌دار loginRequest استخراج می‌شود که در قسمت postData مشخص شده‌است:
        refreshTokenRequest:
          request:
            url: <$ baseUrl $>/account/RefreshToken
            method: POST
            postData:
              mimeType: application/json
              text:
                refreshToken: <$ loginRequest.content.refresh_token $>
          log: <$ logResponse $>
          validate:
            - jsonpath: content.access_token
              type: [string]
            - jsonpath: content.refresh_token
              type: [string]
      در آخر، به قسمت آزمودن نتیجه‌ی درخواست می‌رسیم. در اینجا انتظار داریم که درخواست حاصل که با فرمت json است، دارای دو خاصیت رشته‌ای access_token و refresh_token باشد که بیانگر صدور توکن‌های جدیدی هستند.


      مرحله‌ی 6: آزمایش توکن جدید دریافتی از سرور

      در قسمت قبل، توکن‌های جدیدی صادر شدند که اکنون برای کار با آن‌ها می‌توان از متغیر refreshTokenRequest.content.access_toke استفاده کرد:
        myProtectedApiRequestWithNewToken:
          request:
            url: <$ baseUrl $>/MyProtectedApi
            method: GET
            headers:
              - name: Authorization
                value: Bearer <$ refreshTokenRequest.content.access_token $>
          log: <$ logResponse $>
          validate:
            - jsonpath: content.title
              expect: "Hello from My Protected Controller! [Authorize]"
      در اینجا با استفاده از توکن جدید درخواست نام‌دار refreshTokenRequest، آزمون واحد نوشته شده با موفقیت به پایان می‌رسد (یا باید برسد که اجرای نهایی آزمایش‌ها، آن‌را مشخص می‌کند).


      مرحله‌ی 7: آزمایش منقضی شدن توکنی که در ابتدای کار پس از لاگین دریافت کردیم

      اکنون که refresh token صورت گرفته‌است، دیگر نباید بتوانیم از توکن دریافتی پس از لاگین استفاده کنیم و برنامه باید آن‌را برگشت بزند:
        myProtectedApiRequestWithOldToken:
          request:
            url: <$ baseUrl $>/MyProtectedApi
            method: GET
            headers:
              - name: Authorization
                value: Bearer <$ loginRequest.content.access_token $>
          log: <$ logResponse $>
          validate:
            - jsonpath: status
              expect: 401
      به همین جهت، درخواستی ارسال شده که به نتیجه‌ی درخواست نام‌دار loginRequest اشاره می‌کند. در این حالت برای آزمایش عملیات، اینبار status بازگشتی از سرور که باید 401 باشد، بررسی شده‌است.


      مرحله‌ی 8: آزمایش خروج از سیستم

      در اینجا نیاز است به آدرس account/logout، یک کوئری استرینگ را با کلید refreshToken و مقدار ریفرش‌توکن دریافتی از درخواست نام‌دار refreshTokenRequest، به سمت سرور ارسال کنیم:
        logoutRequest:
          request:
            url: <$ baseUrl $>/account/logout
            method: GET
            headers:
              - name: Authorization
                value: Bearer <$ refreshTokenRequest.content.access_token $>
            queryString:
              - name: refreshToken
                value: <$ refreshTokenRequest.content.refresh_token $>
          log: <$ logResponse $>
          validate:
            - jsonpath: content
              expect: true
      خروجی آزمایش شده‌ی در اینجا، دریافت مقدار true از سمت سرور است.


      مرحله‌ی 9: بررسی عدم امکان دسترسی به منابع محافظت شده‌ی سمت سرور، پس از logout

      در مرحله‌ی قبل، از سیستم خارج شدیم. اکنون می‌خواهیم بررسی کنیم که آیا توکن دریافتی پیشین هنوز معتبر است یا خیر؟ آیا می‌توان هنوز هم به منابع محافظت شده دسترسی یافت یا خیر:
        myProtectedApiRequestWithNewTokenAfterLogout:
          request:
            url: <$ baseUrl $>/MyProtectedApi
            method: GET
            headers:
              - name: Authorization
                value: Bearer <$ refreshTokenRequest.content.access_token $>
          log: <$ logResponse $>
          validate:
            - jsonpath: status
              expect: 401
      به همین جهت هدر Authorization را با اکسس‌توکنی که در مرحله‌ی ریفرش‌توکن دریافت کردیم (پیش از logout)، مقدار دهی می‌کنیم و سپس درخواستی را به یک منبع محافظت شده ارسال می‌کنیم. نتیجه‌ی حاصل باید status code ای مساوی 401 داشته باشد که به معنای برگشت خوردن آن است


      مرحله‌ی 10: اجرای تمام آزمون‌های واحد نوشته شده

      همانطور که در ابتدای بحث نیز عنوان شد فقط کافی است دستور strest JWT.strest.yml را در خط فرمان اجرا کنیم تا آزمون‌های ما به ترتیب اجرا شوند:


      فایل نهایی این آزمایش را در اینجا می‌توانید مشاهده می‌کنید.