مطالب
ارسال ایمیل از طریق سی شارپ
چند وقت پیش درخواستی مبنی بر طراحی یک برنامه‌ی کوچک، برای مدیریت و بایگانی بلیط‌های اخذ شده توسط پرسنل شرکت، از آژانس‌های مسافرتی اعلام شد. از بخش‌های مختلف مورد نیاز برای ساخت این برنامه، بخش ارسال ایمیل را برای نوشتن یک پست آموزشی انتخاب کردم.
در ابتدا لازم است کمی در مورد بعضی از مفاهیم مورد استفاده‌ی در این مطلب توضیح داده شود.
SMTP چیست ؟
SMTP خلاصه شده (Simple mail transfer protocol ) می‌باشد. Smtp بخشی از لایه‌ی برنامه در پروتکل TCP می‌باشد. بطور کلی smtp استانداردی برای ارسال نامه‌های الکترونیکی در اینترنت است. پورت پیش فرض برای ارسال اطلاعات توسط Smtp پورت شماره‌ی 25 است و در حالت ارسال امن و رمز شده اطلاعات (SSL) پورت شماره 465 می‌باشد.
یادآوری : برنامه‌های کامپیوتری برای انتقال بسته‌های اطلاعات از یک شماره در هدر بسته‌ها استفاده می‌کنند که آن را شماره پورت می‌گوییم. لیستی از شماره پورت‌ها به همراه کاربرد آنها را در  اینجا می‌توانید مشاهده کنید.
SMTP مجموعه‌ای از قرارداد‌ها برای ساده شدن ارسال و دریافت اطلاعات با Mail Server را فراهم می‌کند. بطور مثال نام میل سرور‌ها عمدتا به دو شکل smtp.domainname.com  و یا mail.domainname.com نوشته می‌شوند. معمولا smtp برای تنظیمات سرور ایمیل از دو پروتکل pop3 و imap  استفاده می‌کند. این پروتکل‌ها امکانی  فراهم می‌کنند تا نامه‌ها در سرور ذخیره و در فواصلی معین توسط کاربر از سرور به دستگاه‌های هوشمند از قبیل تلفن‌های همراه یا کامپیوتر‌های شخصی منتقل شوند.
در سی شارپ 2 فضای نام System.Net.Sockets  و System.Net برای مدیریت پیاده سازی پروتکل‌های اینترنتی مهیا شده است تا برنامه‌ها بتوانند از طریق آنها اطلاعات را رد و بدل کنند. پروتکل smtp برای ارسال ایمیل در سی شارپ استفاده می‌شود. برای ارسال ایمیل از کلاس Mail در فضای نام System.Net استفاده می‌شود.
تذکر : عموما در بخشی از پنل مدیریت هاست‌ها چگونگی تنظیمات mail server به روش‌های pop3  ،Imap  و حالت رمز شده و امن آنها SSL بیان شده است.
 نمونه ای از ارسال ایمیل در سی شارپ از طریق Mail Server در یک هاست اشتراکی 
               MailMessage mail = new MailMessage();
                //پارامتر این شی همان حالت معرفی شده در تنظیمات ایمیل سرور می‌باشد که پیشتر معرفی شد.
                SmtpClient smtpServer = new SmtpClient("mail.domainName.com");
                mail.Subject = "خرید بلیط";
                mail.From = new MailAddress("info@domainName.com");
                //ایمیل گیرنده نامه
                mail.To.Add("amir2012@gmail.com");
                //متن نامه
                mail.Body = "خرید بلیط کیش به تهران";
                //شماره پورت در اینجا حالت ارسال معمولی و غیر رمز شده مد نظر بوده است
                smtpServer.Port = 25; 
                                                                                            //email address      ,email password
                smtpServer.Credentials = new NetworkCredential("info@domainName.com", "password");
                smtpServer.EnableSsl = false;
                smtpServer.Send(mail);
همانطور که می‌بیند برای پیکربندی ساختار برنامه و ارسال یک نامه‌ی الکترونیکی اشیائی از کلاس SmtpClient و MailMessage ایجاد شده است. شیء mail تنظیمات پیکربندی نامه را انجام می‌دهد و شی smtpServer  تنظیمات ایمیل سرور را در خود نگهداری می‌کند.
نکته: در کد بالا بخش mail.To.Add : گیرنده‌ی نامه می‌توانند 1 یا چند نفر باشد. از این رو خصوصیت To به‌صورت یک مجموعه تعریف شده است.
در این مثال تنظیمات شیء smtp  با توجه به هاست شرکت مورد نظر تنظیم شده است.
مطالب
نصب و راه اندازی SQL Server بر روی لینوکس با استفاده از Docker
 چند وقتی است مایکروسافت تعدادی از محصولات خود را به صورت سورس باز در اختیار برنامه نویسان قرار داده‌است. برای مثال شما می‌توانید در لینوکس یا مک، از نسخه‌ی net core. آن استفاده کنید. در این مقاله روش اجرای sql server را در لینوکس، با استفاده از docker؛ تشریح خواهیم کرد. همچنین با یک پروژه ساده net core.، بر روی دیتابیس add-migration را اجرا کرده و همچنین چند رکورد را در جدولی ثبت می‌کنیم. البته می‌توان نسخه‌ی نصبی sql server را بدون نیاز به docker نیز دانلود و آن را بر روی لینوکس نصب کرد.  


در این مقاله چه چیزی را پوشش خواهیم داد:‌

· راه اندازی داکر
· پیکره‌بندی container image
· وصل شدن به sql
· ساخت یک پروژه ساده net core.
· ایجاد دیتابیس
· ثبت رکورد در دیتابیس

 قبل از هرچیز باید داکر را بر روی سیستم عامل خود (لینوکس) نصب نماید. چون نصب داکر بر روی لینوکس از حوصله‌ی این مقاله خارج می‌باشد، می‌توانید با مراجعه به این لینک docker را نصب کنید. پس از نصب docker، برای اطمینان حاصل نمودن از نصب، با دستور docker version می‌توان کانفیگ داکر را مشاهده کرد:
 


بعد از اینکه docker را بر روی سیستم خود نصب کردید، می‌توانید از دستورات داکر استفاده کنید. در این مقاله می‌خواهیم sql server را بر روی داکر نصب و راه اندازی کنیم.
 

دانلود و نصب sql server بر روی داکر

ابتدا وارد این لینک شوید. همانطور که مشاهده میکنید، SQL Server در 3 نسخه‌ی ویندوز، لینوکس و docker قابل دانلود می‌باشد. چون میخواهیم sql server را بر روی docker نصب کنیم، پس گزینه‌ی docker را انتخاب کنید.


قبل از هرچیز باید Image اس‌‌کیوال سرور را بر روی داکر دانلود نمائید. برای این کار وارد سایت dockerhub شوید و عبارت microsoft/mssql-server-linux را جستجو کنید.


همانطور که در تصویر نیز مشاهد می‌کنید، این بسته 10 میلیون بار دریافت شده‌است! در ادامه دستور زیر را در ترمینال خود Paste کنید و منتظر بمانید تا دانلود شود:
docker pull microsoft/mssql-server-linux:2017-latest
همچنین با اسکرول کردن در این صفحه می‌توانید آموزش نصب و راه ندازی این image را ببینید. بعد از دانلود image مخصوص داکر، با دستور docker images all می‌توانید images دانلود شده را مشاهده کنید. ولی image‌ها به خودی خود  کاربردی ندارند و باید آن‌ها را اجرا کنیم.
برای اجرای image sql از دستور زیر استفاده میکنیم:
 sudo docker run -e 'ACCEPT_EULA=Y' -e 'SA_PASSWORD=<YourStrong!Passw0rd>' \
-p 1433:1433 --name sql1 \
-d mcr.microsoft.com/mssql/server:2017-latest
در این دستور:
docker run –name sql : کار ساخت و اجرای Docker container ای به نام sql را انجام می‌دهد.
'e 'ACCEPT_EULA=Y- : سبب قرار دادن مقدار yes در ACCEPT_EULA که در قسمت environment variables تعریف شده‌است، می‌شود.
Set the  SA_PASSWORD : پسورد  environment variable ای که شما انتخاب می‌کنید.
p 1433:1433- : شماره پورتی که Docker container بر روی آن اجرا میشود.
-d microsoft/mssql-server-linux:2017-latest : نام Image ای که می‌خواهیم اجرا کنیم.

همانطور که مشاهده می‌کنید، Docker container بر روی پورت 1433 اجرا می‌شود. برای مشاهده جزئیات بیشتر، با وارد کردن دستو docker ps a می‌توان لیست containerها و وضعیت آن‌ها را مشاهده کرد.


همانطور که ملاحظه میکنید، در قسمت status، عبارت up به معنای در حال اجرا بودن container است. اگر عبارت دیگری را مشاهده کردید، با دستور dockr start id و وارد کردن شماره image خود می‌توانید آن را اجرا کنید.

تا اینجا توانستیم sql server  را اجرا کنیم. برای توضیحات بیشتر به این لینک مراجعه کنید.
 

وصل شدن به sql
برای وصل شدن به دیتابیس باید connection string دیتابیس مربوطه را داشته باشیم. با توجه به کانفیگ‌هایی که در بالا انجام دادیم، connection string ما به شکل زیر خواهد بود:
Server Host: localhost
Port: 1433
Authentication: SQL Server Authentication
Login: SA
Password: <StrongPasswordYouSet>
اگر کانکشن را به درستی کانفیگ کرده باشید، باید یک دیتابیس به نام انتخابی شما ایجاد شده باشد. در ادامه همین کار را بر روی یک پروژه‌ی  ساده netcore. انجام خواهیم داد. اما برای وصل شدن از طریق docker باید ابتدا bash (دستورات sqlcmd) را بارگذاری کنیم، تا بتوانیم به sqlcmd بر روی container در حال اجرا، دسترسی پیدا کنیم:
sudo docker exec -it sql1 "bash"
پس از آن باید sqlcmd  را به صورت مستقیم و از آدرس فیزیکی سیستم، درون container بارگذاری کنید:
/opt/mssql-tools/bin/sqlcmd -S localhost -U SA -P '<YourNewStrong!Passw0rd>'
اگر دستور فوق با موفقیت اجرا شود ، عبارت 1> در ترمینال به نمایش در می‌آید. یعنی هم اکنون می‌توانید با تایپ دستوارت، آن‌ها را در sqlcmd اجرا کنید:


تا اینجای کار sql server  آماده‌ی اجرا دستورات شما می‌باشد. در ادامه می‌خواهیم چند دستور ساده‌ی sql را بر روی آن اجرا کنیم.


ساخت دیتابیس
با دستور sqlcmd زیر، ابتدا یک دیتابیس را میسازیم:
 CREATE DATABASE TestDB

ساخت جدول
در ادامه، دستور زیر را برای ساخت جدول مینویسیم:
 CREATE TABLE Inventory (id INT, name NVARCHAR(50), quantity INT)

ایجاد رکورد
مرحله بعدی، ایجاد یک رکورد جدید در دیتابیس میباشد:
 INSERT INTO Inventory VALUES (1, 'banana', 150); INSERT INTO Inventory VALUES (2, 'orange', 154);

در آخر با استفاده از دستور go، کوئری‌های بالا را اجرا می‌کنیم. اکنون باید یک دیتابیس جدید به نام TestDB و یک جدول جدید نیز به نام Inventory همچنین یک رکورد جدید در آن ثبت شده باشد. برای مشاهده‌ی تغییرات بالا، از دستورات زیر استفاده میکنیم:
- با دستور زیر لیست دیتابیس‌های موجود را می‌توان دید:
 SELECT Name from sys.Databases
- کو ئری select از دیتابیس:
 SELECT * FROM Inventory WHERE quantity > 152;

و با استفاده از دستور quit میتوانید از cmd خارج شوید.

تا اینجا توانستیم docker را بر روی سیستم راه ندازی و همچنین sql server  را بر روی آن نصب و اجرا کنیم. همچنین با دستورات sqlcmd توانستیم بر روی sql کوئری بزنیم.


ساخت و وصل شدن یک پروژه‌ی net core. و وصل شدن به sql server

حال میخواهیم با یک پروژه‌ی ساده‌ی net core. به sql server فوق وصل شده و یک جدول را به دیتابیس مذکور اضافه کرده و یک کوئری اضافه کردن رکوردی را به آن جدول بنویسیم. برای شروع، یک پروژه‌ی خالی net core. را ایجاد می‌کنیم. برای مثال یک پروژه‌ی api را ایجاد میکنیم:
dotnet new webapi -o dockerapi
سپس دو پکیج زیر را به آن اضافه میکنیم:
dotnet add package Microsoft.EntityFrameworkCore.SqlServer
dotnet add package Microsoft.EntityFrameworkCore.Design
در این مثال می‌خواهیم جدول Students را ایجاد و یک رکورد را در آن ثبت نماییم. پس یک کلاس را به نام Students ساخته و property‌های زیر را در آن مینویسیم:
public class Students
{
       public int Id { get; set; }
       public string Name { get; set; }
       public string Phone { get; set; }
}
مرحله‌ی بعد، ساخت context میباشد. برای اینکه وارد جزئیات نشویم، از قابلیت Scaffold استفاده می‌کنیم و context را تولید میکنیم:
 dotnet ef dbcontext scaffold "Server=localhost,1433\\Catalog=tutorial_database;Database=<YOUR_DATABASE_NAME>;User=SA;Password=<StrongPasswordYouSet>;" Microsoft.EntityFrameworkCore.SqlServer
پس از اجرای دستور بالا، context ساخته میشود. حال دورن context، یک DbSet را از students ایجاد میکنیم. بعد نوبت به تنظیم کردن connection string می‌رسد. داخل کانتکست، connection string را تنظیم کنید. همچنین connection string داخل appsettings.json  را نیز تنظیم کنید:
"ConnectionStrings": {
  "TestingDatabase": "Server=localhost:1433\\Database=<YourDatabaseName>;User=SA;Password=<StrongPasswordYouSet>;"
}
بعد از تنظیم کردن connection string، باید migration را بزنیم تا تغییرات context را مشاهده کنیم. با دستور زیر migration خود را اضافه کنید:
 dotnet ef migrations add <NAME_OF_MIGRATION>


همانطور که مشاهده می‌کنید، migrations اضافه شده و موجودیت هم اضافه شده‌است. حال باید بر روی migrations خود آپدیت بزنیم:
ef database update

همانطور که در شکل بالا نیز مشاهده می‌کنید، دیتابیس ما ایجاد شده‌است. حال به docker برمی‌گردیم و با دستور زیر، لیست تمام دیتابیس‌های موجود را نمایش میدهیم:

همانطور که مشاهده می‌کنید، دیتابیس برای ما ایجاد شده. با دستور زیر می‌توان جدول دیتابیس را مشاهده کرد:
 SELECT TABLE_NAME FROM dockerdb.INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE = 'BASE TABLE'
مطالب
شروع کار با ASP.NET Web API 2
HTTP تنها برای به خدمت گرفتن صفحات وب نیست. این پروتکل همچنین پلتفرمی قدرتمند برای ساختن API هایی است که سرویس‌ها و داده را در دسترس قرار می‌دهند. این پروتکل ساده، انعطاف پذیر و در همه جا حاضر است. هر پلتفرمی که فکرش را بتوانید بکنید کتابخانه ای برای HTTP دارد، بنابراین سرویس‌های HTTP می‌توانند بازه بسیار گسترده ای از کلاینت‌ها را پوشش دهند، مانند مرورگرها، دستگاه‌های موبایل و اپلیکیشن‌های مرسوم دسکتاپ.

ASP.NET Web API فریم ورکی برای ساختن API‌های وب بر روی فریم ورک دات نت است. در این مقاله با استفاده از این فریم ورک، API وبی خواهیم ساخت که لیستی از محصولات را بر می‌گرداند. صفحه وب کلاینت، با استفاده از jQuery نتایج را نمایش خواهد داد.


یک پروژه Web API بسازید

در ویژوال استودیو 2013 پروژه جدیدی از نوع ASP.NET Web Application بسازید و نام آن را "ProductsApp" انتخاب کنید.

در دیالوگ New ASP.NET Project قالب Empty را انتخاب کنید و در قسمت "Add folders and core references for" گزینه Web API را انتخاب نمایید.

می توانید از قالب Web API هم استفاده کنید. این قالب با استفاده از ASP.NET MVC صفحات راهنمای API را خواهد ساخت. در این مقاله از قالب Empty استفاده میکنیم تا تمرکز اصلی، روی خود فریم ورک Web API باشد. بطور کلی برای استفاده از این فریم ورک لازم نیست با ASP.NET MVC آشنایی داشته باشید.

افزودن یک مدل

یک مدل (model) آبجکتی است که داده اپلیکیشن شما را معرفی می‌کند. ASP.NET Web API می‌تواند بصورت خودکار مدل شما را به JSON, XML و برخی فرمت‌های دیگر مرتب (serialize) کند، و سپس داده مرتب شده را در بدنه پیام HTTP Response بنویسد. تا وقتی که یک کلاینت بتواند فرمت مرتب سازی داده‌ها را بخواند، می‌تواند آبجکت شما را deserialize کند. اکثر کلاینت‌ها می‌توانند XML یا JSON را تفسیر کنند. بعلاوه کلاینت‌ها می‌توانند فرمت مورد نظرشان را با تنظیم Accept header در پیام HTTP Request مشخص کنند.

بگذارید تا با ساختن مدلی ساده که یک محصول (product) را معرفی میکند شروع کنیم.

کلاس جدیدی در پوشه Models ایجاد کنید.

نام کلاس را به "Product" تغییر دهید، و خواص زیر را به آن اضافه کنید.

namespace ProductsApp.Models
{
    public class Product
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public string Category { get; set; }
        public decimal Price { get; set; }
    }
}

افزودن یک کنترلر

در Web API کنترلر‌ها آبجکت هایی هستند که درخواست‌های HTTP را مدیریت کرده و آنها را به اکشن متدها نگاشت می‌کنند. ما کنترلری خواهیم ساخت که می‌تواند لیستی از محصولات، یا محصولی بخصوص را بر اساس شناسه برگرداند. اگر از ASP.NET MVC استفاده کرده اید، با کنترلرها آشنا هستید. کنترلرهای Web API مشابه کنترلر‌های MVC هستند، با این تفاوت که بجای ارث بری از کلاس Controller از کلاس ApiController مشتق می‌شوند.

کنترلر جدیدی در پوشه Controllers ایجاد کنید.

در دیالوگ Add Scaffold گزینه Web API Controller - Empty را انتخاب کرده و روی Add کلیک کنید.

در دیالوگ Add Controller نام کنترلر را به "ProductsController" تغییر دهید و روی Add کلیک کنید.

توجه کنید که ملزم به ساختن کنترلرهای خود در پوشه Controllers نیستید، و این روش صرفا قراردادی برای مرتب نگاه داشتن ساختار پروژه‌ها است. کنترلر ساخته شده را باز کنید و کد زیر را به آن اضافه نمایید.

using ProductsApp.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Web.Http;

namespace ProductsApp.Controllers
{
    public class ProductsController : ApiController
    {
        Product[] products = new Product[] 
        { 
            new Product { Id = 1, Name = "Tomato Soup", Category = "Groceries", Price = 1 }, 
            new Product { Id = 2, Name = "Yo-yo", Category = "Toys", Price = 3.75M }, 
            new Product { Id = 3, Name = "Hammer", Category = "Hardware", Price = 16.99M } 
        };

        public IEnumerable<Product> GetAllProducts()
        {
            return products;
        }

        public IHttpActionResult GetProduct(int id)
        {
            var product = products.FirstOrDefault((p) => p.Id == id);
            if (product == null)
            {
                return NotFound();
            }
            return Ok(product);
        }
}
برای اینکه مثال جاری را ساده نگاه داریم، محصولات مورد نظر در یک آرایه استاتیک ذخیره شده اند. مسلما در یک اپلیکیشن واقعی برای گرفتن لیست محصولات از دیتابیس یا منبع داده ای دیگر کوئری می‌گیرید.

کنترلر ما دو متد برای دریافت محصولات تعریف می‌کند:

  • متد GetAllProducts لیست تمام محصولات را در قالب یک <IEnumerable<Product بر می‌گرداند.
  • متد GetProductById سعی می‌کند محصولی را بر اساس شناسه تعیین شده پیدا کند.

همین! حالا یک Web API ساده دارید. هر یک از متدهای این کنترلر، به یک یا چند URI پاسخ می‌دهند:

 URI  Controller Method
api/products/  GetAllProducts
 api/products/id/  GetProductById

برای اطلاعات بیشتر درباره نحوه نگاشت درخواست‌های HTTP به اکشن متدها توسط Web API به این لینک مراجعه کنید.


فراخوانی Web API با جاوا اسکریپت و jQuery

در این قسمت یک صفحه HTML خواهیم ساخت که با استفاده از AJAX متدهای Web API را فراخوانی می‌کند. برای ارسال درخواست‌های آژاکسی و بروز رسانی صفحه بمنظور نمایش نتایج دریافتی از jQuery استفاده میکنیم.

در پنجره Solution Explorer روی نام پروژه کلیک راست کرده و گزینه Add, New Item را انتخاب کنید.

در دیالوگ Add New Item قالب HTML Page را انتخاب کنید و نام فایل را به "index.html" تغییر دهید.

حال محتوای این فایل را با لیست زیر جایگزین کنید.

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
  <title>Product App</title>
</head>
<body>

  <div>
    <h2>All Products</h2>
    <ul id="products" />
  </div>
  <div>
    <h2>Search by ID</h2>
    <input type="text" id="prodId" size="5" />
    <input type="button" value="Search" onclick="find();" />
    <p id="product" />
  </div>

  <script src="http://ajax.aspnetcdn.com/ajax/jQuery/jquery-2.0.3.min.js"></script>
  <script>
    var uri = 'api/products';

    $(document).ready(function () {
      // Send an AJAX request
      $.getJSON(uri)
          .done(function (data) {
            // On success, 'data' contains a list of products.
            $.each(data, function (key, item) {
              // Add a list item for the product.
              $('<li>', { text: formatItem(item) }).appendTo($('#products'));
            });
          });
    });

    function formatItem(item) {
      return item.Name + ': $' + item.Price;
    }

    function find() {
      var id = $('#prodId').val();
      $.getJSON(uri + '/' + id)
          .done(function (data) {
            $('#product').text(formatItem(data));
          })
          .fail(function (jqXHR, textStatus, err) {
            $('#product').text('Error: ' + err);
          });
    }
  </script>
</body>
</html>
راه‌های مختلفی برای گرفتن jQuery وجود دارد، در این مثال از Microsoft Ajax CDN استفاده شده. می‌توانید این کتابخانه را از http://jquery.com دانلود کنید و بصورت محلی استفاده کنید. همچنین قالب پروژه‌های  Web API این کتابخانه را به پروژه نیز اضافه می‌کنند.


گرفتن لیستی از محصولات

برای گرفتن لیستی از محصولات، یک درخواست HTTP GET به آدرس "api/products/" ارسال کنید.

تابع getJSON یک درخواست آژاکسی ارسال می‌کند. پاسخ دریافتی هم آرایه ای از آبجکت‌های JSON خواهد بود. تابع done در صورت موفقیت آمیز بودن درخواست، اجرا می‌شود. که در این صورت ما DOM را با اطلاعات محصولات بروز رسانی می‌کنیم.

$(document).ready(function () {
    // Send an AJAX request
    $.getJSON(apiUrl)
        .done(function (data) {
            // On success, 'data' contains a list of products.
            $.each(data, function (key, item) {
                // Add a list item for the product.
                $('<li>', { text: formatItem(item) }).appendTo($('#products'));
            });
        });
});

گرفتن محصولی مشخص

برای گرفتن یک محصول توسط شناسه (ID) آن کافی است یک درخواست HTTP GET به آدرس "api/products/id/" ارسال کنید.

function find() {
    var id = $('#prodId').val();
    $.getJSON(apiUrl + '/' + id)
        .done(function (data) {
            $('#product').text(formatItem(data));
        })
        .fail(function (jqXHR, textStatus, err) {
            $('#product').text('Error: ' + err);
        });
}
برای این کار هنوز از getJSON برای ارسال درخواست آژاکسی استفاده می‌کنیم، اما اینبار شناسه محصول را هم به آدرس درخواستی اضافه کرده ایم. پاسخ دریافتی از این درخواست، اطلاعات یک محصول با فرمت JSON است.


اجرای اپلیکیشن

اپلیکیشن را با F5 اجرا کنید. صفحه وب باز شده باید چیزی مشابه تصویر زیر باشد.

برای گرفتن محصولی مشخص، شناسه آن را وارد کنید و روی Search کلیک کنید.

اگر شناسه نامعتبری وارد کنید، سرور یک خطای HTTP بر می‌گرداند.


استفاده از F12 برای مشاهده درخواست‌ها و پاسخ ها

هنگام کار با سرویس‌های HTTP، مشاهده‌ی درخواست‌های ارسال شده و پاسخ‌های دریافتی بسیار مفید است. برای اینکار می‌توانید از ابزار توسعه دهندگان وب استفاده کنید، که اکثر مرورگرهای مدرن، پیاده سازی خودشان را دارند. در اینترنت اکسپلورر می‌توانید با F12 به این ابزار دسترسی پیدا کنید. به برگه Network بروید و روی Start Capturing کلیک کنید. حالا صفحه وب را مجددا بارگذاری (reload) کنید. در این مرحله اینترنت اکسپلورر ترافیک HTTP بین مرورگر و سرور را تسخیر می‌کند. می‌توانید تمام ترافیک HTTP روی صفحه جاری را مشاهده کنید.

به دنبال آدرس نسبی "api/products/" بگردید و آن را انتخاب کنید. سپس روی Go to detailed view کلیک کنید تا جزئیات ترافیک را مشاهده کنید. در نمای جزئیات، می‌توانید header‌ها و بدنه درخواست‌ها و پاسخ‌ها را ببینید. مثلا اگر روی برگه Request headers کلیک کنید، خواهید دید که اپلیکیشن ما در Accept header داده‌ها را با فرمت "application/json" درخواست کرده است.

اگر روی برگه Response body کلیک کنید، می‌توانید ببینید چگونه لیست محصولات با فرمت JSON سریال شده است. همانطور که گفته شده مرورگرهای دیگر هم قابلیت‌های مشابهی دارند. یک ابزار مفید دیگر Fiddler است. با استفاده از این ابزار می‌توانید تمام ترافیک HTTP خود را مانیتور کرده، و همچنین درخواست‌های جدیدی بسازید که این امر کنترل کاملی روی HTTP headers به شما می‌دهد.


قدم‌های بعدی

  • برای یک مثال کامل از سرویس‌های HTTP که از عملیات POST,PUT و DELETE پشتیبانی می‌کند به این لینک مراجعه کنید.
  • برای اطلاعات بیشتر درباره طراحی واکنش گرا در کنار سرویس‌های HTTP به این لینک مراجعه کنید، که اپلیکیشن‌های تک صفحه ای (SPA) را بررسی می‌کند.
نظرات مطالب
نحوه صحیح تولید Url در ASP.NET MVC
فرم ادیت رو به صورت strongly typed از نوع یک partial view مستقل درست کنید.
سپس در کنترلر مرتبط، یک اکشن متد را مخصوص رندر کردن این partial view در نظر بگیرید. کار آن دریافت اطلاعات مرتبط با Model ارسالی به همین partial view است. سپس در آخر کار آن هم خواهیم داشت:

return PartialView("_MyPartialViewName", data);
حالا فراخوانی این اکشن متد توسط jQuery Ajax سبب پر شدن خودکار فیلدهای فرم strongly typed شما هم می‌شود. در حین درخواست، اطلاعات مدل از بانک اطلاعاتی دریافت شده و به Partial view ارسال می‌شود. چون strongly typed است، فیلدهای آن به صورت خودکار پر شده و نهایتا کل partial view به صورت یک رشته آماده شده در اختیار شما خواهد بود. بنابراین، قسمت عمده‌ای از کدهای سمت کاربر فوق کاهش پیدا می‌کنند. چون یک view کامل حاضر و آماده از سرور دریافت شده است که باید به صفحه توسط jQuery اضافه شود.

+
لطفا اینجا رو تبدیل به یک انجمن عمومی نکنید. عنوان بحث ساخت Url بود ... بعد تغییر جهت پیدا کرد به یک عنوان دیگر.
با تشکر از توجه شما.
 
نظرات مطالب
Blazor 5x - قسمت 27 - برنامه‌ی Blazor WASM - کار با سرویس‌های Web API
این پروژه برای اجرا، نیاز به اجرای همزمان دو پروژه را دارد که بهتر است ابتدا Web API اجرا شود و سپس کلاینتی که قرار است با آن کار کند:
الف) پروژه‌ی Web API
در این پروژه دو تغییر زیر انجام شده:
- در فایل HotelManagement\BlazorWasm\BlazorWasm.WebApi\Properties\launchSettings.json شماره پورت‌ها به 5001 و 5000 تنظیم شدند که البته چون بر روی SSL اجرا می‌شود، مقدار 5001 آن مهم است:
https://localhost:5001;http://localhost:5000
- در فایل HotelManagement\BlazorWasm\BlazorWasm.WebApi\appsettings.json شماره پورت کلاینت مشخص شده که در قسمت ب تنظیم می‌شود:
"Client_URL": "https://localhost:5002/"

ب) پروژه‌ی کلاینت
در این پروژه هم دو تغییر زیر انجام شده:
- در فایل HotelManagement\BlazorWasm\BlazorWasm.Client\Properties\launchSettings.json  شماره پورت‌ها به 5002 و 5003 تنظیم شدند که البته چون بر روی SSL اجرا می‌شود، مقدار 5002 آن مهم است:
https://localhost:5002;http://localhost:5003
- در فایل HotelManagement\BlazorWasm\BlazorWasm.Client\wwwroot\appsettings.json شماره پورت اتصال به Web API مشخص شده که همان 5001 قسمت الف است:
"BaseAPIUrl": "https://localhost:5001/"

این نکات را برای کار با IIS Express هم باید رعایت کنید و در فایل‌های launchSettings.json آن‌ها تنظیم کنید (و اگر شماره پورت ssl آن‌ها یکی نیست، یعنی تنظیم خوبی دارند). بعد از این تنظیمات، فایل‌های appsettings.json پروژه‌ها را هم یافته و شماره پورت‌های کلاینت و Web API را مانند مثال فوق و بر اساس شماره‌هایی که تنظیم کردید، دقیق تنظیم کنید تا کلاینت بداند که قرار است درخواست‌ها را دقیقا به چه آدرسی ارسال کند.
مطالب
آشنایی با ساختار IIS قسمت اول
در مقاله قبل در مورد نحوه ذخیره سازی در حافظه نوشتیم و به user mode و kernel mode اشاراتی کردیم که می‌توانید به آن رجوع کنید.
در این سری مقالات قصد داریم به بررسی اجزا و روند کاری موجود در IIS بپردازیم که چگونه IIS کار می‌کند و شامل چه بخش هایی می‌شود. مطمئنا آشنایی با این بخش‌ها در روند شناسایی رفتارهای وب اپلیکیشن‌ها و واکنش‌های سرور، کمک زیادی به ما خواهد کرد. در اینجا نسخه IIS7 را به عنوان مرجع در نظر گرفته‌ایم.
وب سرور IIS در عبارت مخفف Internet information services به معنی سرویس‌های اطلاعاتی اینترنت می‌باشد. IIS شامل کامپوننت‌های زیادی است که هر کدام ازآن‌ها کار خاصی را انجام میدهند؛ برای مثال گوش دادن به درخواست‌های ارسال شده به سرور، مدیریت فرآیندها Process و خواندن فایل‌های پیکربندی Configuration؛ این اجزا شامل protocol listener ،Http.sys و WSA و .. می‌شوند.
Protocol Listeners
این پروتکل‌ها به درخواست‌های رسیده گوش کرده و آن‌ها را مورد پردازش قرار می‌دهند و پاسخی را به درخواست کننده، ارسال می‌کنند. هر listener بر اساس نوع پروتکل متفاوت هست. به عنوان مثال کلاینتی، درخواست صفحه‌ای را می‌کند و http listener که به آن Http.sys می‌گویند به آن پاسخ می‌دهد. به طور پیش فرض http.sys به درخواست‌های http و https گوش فرا می‌دهد، این کامپوننت از IIS6 اضافه شده است ولی در نسخه 7 از SSL نیز پشتیبانی می‌کند.
Http.sys یا Hypertext transfer protocol stack
کار این واحد در سه مرحله دریافت درخواست، ارسال آن به واحد پردازش IIS و ارسال پاسخ به کلاینت است؛ قبل از نسخه 6 از Winsock یا windows socket api  که یک کامپوننت user-mod بود استفاده می‌شد ولی Http.sys یک کامپوننت Kernel-mod هست.

Http.sys مزایای زیر را به همراه دارد:

  • صف درخواست مد کرنل: به خاطر اینکه کرنل مستقیما درخواست‌ها را به پروسه‌های مربوطه میفرستد و اگر پروسه موجود نباشد، درخواست را در صف گذاشته تا بعدا پروسه مورد نظر آن را از صف بیرون بکشد.
  • برای درخواست‌ها یک پیش پردازش و همچنین اعمال فیلترهای امنیتی اعمال می‌گردد. 
  • عملیات کش کردن تماما در محیط کرنل مد صورت می‌گیرد؛ بدون اینکه به حالت یوزرمد سوییچ کند. مد کرنل دسترسی بسیار راحت و مستقیمی را برای استفاده از منابع دارد و لازم نیست مانند مد کاربر به لایه‌های زیرین، درخواست کاری را بدهد؛ چرا که خود مستقیما وارد عمل می‌شود و برداشته شدن واسط در سر راه، موجب افزایش عمل caching می‌شود. همچنین دسترسی به کش باعث می‌شود که مستقیما پاسخ از کش به کاربر برسد و توابع پردازشی در حافظه بارگذاری نشوند. البته این کش کردن محدودیت هایی را هم به همراه دارد:
    1. کش کرنل به صورت پیش فرض بر روی صفحات ایستا فعال شده است؛ نه برای صفحاتی با محتوای پویا که البته این مورد قابل تغییر است که نحوه این تغییر را پایینتر توضیح خواهیم داد.
    2. اگر آدرس درخواستی شامل کوئری باشد صفحه کش نخواهد شد:    http://www.site.info/postarchive.htm?id=25 
    3. برای پاسخ ازمکانیزم‌های فشرده سازی پویا استفاده شده باشد مثل gzip کش نخواهد شد
    4. صفحه درخواست شده صفحه اصلی سایت باشد کش نخواهد شد :   http://www.dotnettip.info ولی اگر درخواست بدین صورت باشه http://www.domain.com/default.htm  کش خواهد کرد.
    5. درخواست به صورت ناشناس anonymous نباشد  و نیاز به authentication داشته باشد کش نخواهد شد (یعنی در هدر شامل گزینه authorization می‌باشد).
    6. درخواست باید از نوع نسخه http1 به بعد باشد.
    7. اگر درخواست شامل Entity-body باشد کش نخواهد کرد.
    8. درخواست شامل If-Range/Range header باشد کش نمی‌شود.
    9. کل حجم response بییشتر از اندازه تعیین شده باشد کش نخواهد گردید، این اندازه در کلید ریجستری UriMaxUriBytes قرار دارد. اطلاعات بیشتر
    10. اندازه هدر بیشتر از اندازه تعیین شده باشد که عموما اندازه تعیین شده یک کیلو بایت است.
    11. کش پر باشد، کش انجام نخواهد گرفت.
    برای فعال سازی کش کرنل راهنمای زیر را دنبال کنید:
    گزینه output cache را در IIS، فعال کنید و سپس گزینه Add را بزنید. کادر add cache rule که باز شود، از شما میخواهد یکی از دو نوع کش مد کاربر و مد کرنل را انتخاب کنید و  مشخص کنید چه نوع فایل‌هایی (مثلا aspx) از این قوانین پیروری کنند و مکانیزم کش کردن به سه روش جلوگیری از کش کردن، کش زمان دار و کش بر اساس آخرین تغییر فایل انجام گردد.


    برای تعیین مقدار سایز کش response که در بالا اشاره کردیم می‌توانید در همان پنجره، گزینه edit feature settings را انتخاب کنید.


    این قسمت از مطلب که به نقل از مقاله  آقای Karol Jarkovsky در این آدرس است یک سری تست هایی با نرم افزار(Web Capacity Analysis Tool (WCAT  گرفته است که به نتایج زیر دست پیدا کرده است:
    Kernel Cache Disabled    4 clients/160 threads/30 sec      257 req/sec
    Kernel Cache Enabled     4 clients/160 threads/30 sec      553 req/sec 
    همانطور که می‌بینید نتیجه فعال سازی کش کرنل پاسخ به بیش از دو برابر درخواست در حالت غیرفعال آن است که یک عدد فوق العاده به حساب میاد.
    برای اینکه خودتان هم تست کرده باشید در این آدرس  برنامه را دانلود کنید و به دنبال فایل request.cfg بگردید و از صحت پارامترهای server و url اطمینان پیدا کنید. در گام بعدی 5 پنجره خط فرمان باز کرده و در یکی از آن‌ها دستور netsh http show cachestate را بنویسید تا تمامی وروردی‌های entry که در کش کرنل ذخیره شده اند لیست شوند. البته در اولین تست کش را غیرفعال کنید و به این ترتیب نباید چیزی نمایش داده شود. در همان پنجره فرمان wcctl –a localhost –c config.cfg –s request.cfg  را زده تا کنترلر برنامه در وضعیت listening قرار بگیرد. در 4 پنجره دیگر فرمان wcclient localhost از شاخه کلاینت را نوشته تا تست آغاز شود. بعد از انجام تست به شاخه نصب کنترلر WCAT رفته و فایل log را بخوانید و اگر دوباره دستور نمایش کش کرنل را بزنید باید خالی باشد. حالا کش را فعال کنید و دوباره عملیات تست را از سر بگیرید و اگر دستور netsh را ارسال کنید باید کش کرنل دارای ورودی باشد.
    برای تغییرات در سطح http.sys می‌توانید از ریجستری کمک بگیرید. در اینجا تعداد زیادی از تنظیمات ذخیره شده در ریجستری برای http.sys لیست شده است.
    نظرات نظرسنجی‌ها
    چه نوع محیط کاری را بیشتر ترجیح می‌دهید؟
    عادتشون بدید از برنامه‌های issue tracker شما استفاده کنند. آنلاین مشکلات را دریافت کنید و آنلاین رفع اشکال.  ساده‌ترینش درست کردن یگ گروه تلگرامی برای پشتیبانی برنامه است.
    مطالب
    احراز هویت و اعتبارسنجی کاربران در برنامه‌های Angular - قسمت دوم - سرویس اعتبارسنجی
    در قسمت قبل، ساختار ابتدایی کلاینت Angular را تدارک دیدیم. در این قسمت قصد داریم سرویسی که زیر ساخت کامپوننت لاگین و عملیات ورود به سیستم را تامین می‌کند، تکمیل کنیم.


    تعریف تزریق وابستگی تنظیمات برنامه

    در مطلب «تزریق وابستگی‌ها فراتر از کلاس‌ها در برنامه‌های Angular» با روش تزریق ثوابت برنامه آشنا شدیم. در این مثال، برنامه‌ی کلاینت بر روی پورت 4200 اجرا می‌شود و برنامه‌ی سمت سرور وب، بر روی پورت 5000. به همین جهت نیاز است این آدرس پایه سمت سرور را در تمام قسمت‌های برنامه که با سرور کار می‌کنند، در دسترس داشته باشیم و روش مناسب برای پیاده سازی آن همان قسمت «تزریق تنظیمات برنامه توسط تامین کننده‌ی مقادیر» مطلب یاد شده‌است. به همین جهت فایل جدید src\app\core\services\app.config.ts را در پوشه‌ی core\services برنامه ایجاد می‌کنیم:
    import { InjectionToken } from "@angular/core";
    
    export let APP_CONFIG = new InjectionToken<string>("app.config");
    
    export interface IAppConfig {
      apiEndpoint: string;
      loginPath: string;
      logoutPath: string;
      refreshTokenPath: string;
      accessTokenObjectKey: string;
      refreshTokenObjectKey: string;
    }
    
    export const AppConfig: IAppConfig = {
      apiEndpoint: "http://localhost:5000/api",
      loginPath: "account/login",
      logoutPath: "account/logout",
      refreshTokenPath: "account/RefreshToken",
      accessTokenObjectKey: "access_token",
      refreshTokenObjectKey: "refresh_token"
    };
    در اینجا APP_CONFIG یک توکن منحصربفرد است که از آن جهت یافتن مقدار AppConfig که از نوع اینترفیس IAppConfig تعریف شده‌است، در سراسر برنامه استفاده خواهیم کرد.
    سپس تنظیمات ابتدایی تزریق وابستگی‌های IAppConfig را در فایل src\app\core\core.module.ts به صورت ذیل انجام می‌دهیم:
    import { AppConfig, APP_CONFIG } from "./app.config";
    
    @NgModule({
      providers: [
        { provide: APP_CONFIG, useValue: AppConfig }
      ]
    })
    export class CoreModule {}
    اکنون هر سرویس و یا کامپوننتی در سراسر برنامه که نیاز به تنظیمات AppConfig را داشته باشد، کافی است با استفاده از ویژگی Inject(APP_CONFIG)@ آن‌را درخواست کند.


    طراحی سرویس Auth

    پس از لاگین باید بتوان به اطلاعات اطلاعات کاربر وارد شده‌ی به سیستم، در تمام قسمت‌های برنامه دسترسی پیدا کرد. به همین جهت نیاز است این اطلاعات را در یک سرویس سراسری singleton قرار داد تا همواره یک وهله‌ی از آن در کل برنامه قابل استفاده باشد. مرسوم است این سرویس را AuthService بنامند. بنابراین محل قرارگیری این سرویس سراسری در پوشه‌ی Core\services و محل تعریف آن در قسمت providers آن خواهد بود. به همین جهت ابتدا ساختار این سرویس را با دستور ذیل ایجاد می‌کنیم:
     ng g s Core/services/Auth
    با این خروجی:
       create src/app/Core/services/auth.service.ts (110 bytes)
    و سپس تعریف آن‌را به مدخل providers ماژول Core اضافه می‌کنیم:
    import { AuthService } from "./services/auth.service";
    
    @NgModule({
      providers: [
        // global singleton services of the whole app will be listed here.
        BrowserStorageService,
        AuthService,
        { provide: APP_CONFIG, useValue: AppConfig }
      ]
    })
    export class CoreModule {}

    در ادامه به تکمیل AuthService خواهیم پرداخت و قسمت‌های مختلف آن‌را مرور می‌کنیم.


    اطلاع رسانی به کامپوننت Header در مورد وضعیت لاگین

    در مطلب «صدور رخدادها از سرویس‌ها به کامپوننت‌ها در برنامه‌های Angular» با نحوه‌ی کار با BehaviorSubject آشنا شدیم. در اینجا می‌خواهیم توسط آن، پس از لاگین موفق، وضعیت لاگین را به کامپوننت هدر صادر کنیم، تا لینک لاگین را مخفی کرده و لینک خروج از سیستم را نمایش دهد:
    import { BehaviorSubject } from "rxjs/BehaviorSubject";
    
    @Injectable()
    export class AuthService {
    
      private authStatusSource = new BehaviorSubject<boolean>(false);
      authStatus$ = this.authStatusSource.asObservable();
    
      constructor() {
        this.updateStatusOnPageRefresh();
      }
    
      private updateStatusOnPageRefresh(): void {
        this.authStatusSource.next(this.isLoggedIn());
      }
    اکنون تمام کامپوننت‌های برنامه می‌توانند مشترک $authStatus شده و همواره آخرین وضعیت لاگین را دریافت کنند و نسبت به تغییرات آن عکس العمل نشان دهند (برای مثال قسمتی را نمایش دهند و یا قسمتی را مخفی کنند).
    در اینجا در سازنده‌ی کلاس، بر اساس خروجی متد وضعیت لاگین شخص، برای اولین بار، متد next این BehaviorSubject فراخوانی می‌شود. علت قرار دادن این متد در سازنده‌ی کلاس سرویس، عکس العمل نشان دادن به refresh کامل صفحه، توسط کاربر است و یا عکس العمل نشان دادن به وضعیت به‌خاطر سپاری کلمه‌ی عبور، در اولین بار مشاهده‌ی سایت و برنامه. در این حالت متد isLoggedIn، کش مرورگر را بررسی کرده و با واکشی توکن‌ها و اعتبارسنجی آن‌ها، گزارش وضعیت لاگین را ارائه می‌دهد. پس از آن، خروجی آن (true/false) به مشترکین اطلاع رسانی می‌شود.
    در ادامه، متد next این  BehaviorSubject را در متدهای login و logout نیز فراخوانی خواهیم کرد.


    تدارک ذخیره سازی توکن‌ها در کش مرورگر

    از طرف سرور، دو نوع توکن access_token و refresh_token را دریافت می‌کنیم. به همین جهت یک enum را جهت مشخص سازی آن‌ها تعریف خواهیم کرد:
    export enum AuthTokenType {
       AccessToken,
       RefreshToken
    }
    سپس باید این توکن‌ها را پس از لاگین موفق در کش مرورگر ذخیره کنیم که با مقدمات آن در مطلب «ذخیره سازی اطلاعات در مرورگر توسط برنامه‌های Angular» پیشتر آشنا شدیم. از همان سرویس BrowserStorageService مطلب یاد شده، در اینجا نیز استفاده خواهیم کرد:
    import { BrowserStorageService } from "./browser-storage.service";
    
    export enum AuthTokenType {
      AccessToken,
      RefreshToken
    }
    
    @Injectable()
    export class AuthService {
    
      private rememberMeToken = "rememberMe_token";
    
      constructor(private browserStorageService: BrowserStorageService) {  }
    
      rememberMe(): boolean {
        return this.browserStorageService.getLocal(this.rememberMeToken) === true;
      }
    
      getRawAuthToken(tokenType: AuthTokenType): string {
        if (this.rememberMe()) {
          return this.browserStorageService.getLocal(AuthTokenType[tokenType]);
        } else {
          return this.browserStorageService.getSession(AuthTokenType[tokenType]);
        }
      }
    
      deleteAuthTokens() {
        if (this.rememberMe()) {
          this.browserStorageService.removeLocal(AuthTokenType[AuthTokenType.AccessToken]);
          this.browserStorageService.removeLocal(AuthTokenType[AuthTokenType.RefreshToken]);
        } else {
          this.browserStorageService.removeSession(AuthTokenType[AuthTokenType.AccessToken]);
          this.browserStorageService.removeSession(AuthTokenType[AuthTokenType.RefreshToken]);
        }
        this.browserStorageService.removeLocal(this.rememberMeToken);
      }
    
      private setLoginSession(response: any): void {
        this.setToken(AuthTokenType.AccessToken, response[this.appConfig.accessTokenObjectKey]);
        this.setToken(AuthTokenType.RefreshToken, response[this.appConfig.refreshTokenObjectKey]);
      }
    
      private setToken(tokenType: AuthTokenType, tokenValue: string): void {
        if (this.rememberMe()) {
          this.browserStorageService.setLocal(AuthTokenType[tokenType], tokenValue);
        } else {
          this.browserStorageService.setSession(AuthTokenType[tokenType], tokenValue);
        }
      }
    }
    ابتدا سرویس BrowserStorageService به سازنده‌ی کلاس تزریق شده‌است و سپس نیاز است بر اساس گزینه‌ی «به‌خاطر سپاری کلمه‌ی عبور»، نسبت به انتخاب محل ذخیره سازی توکن‌ها اقدام کنیم. اگر گزینه‌ی rememberMe توسط کاربر در حین لاگین انتخاب شود، از local storage ماندگار و اگر خیر، از session storage فرار مرورگر برای ذخیره سازی توکن‌ها و سایر اطلاعات مرتبط استفاده خواهیم کرد.


    - متد rememberMe مشخص می‌کند که آیا وضعیت به‌خاطر سپاری کلمه‌ی عبور توسط کاربر انتخاب شده‌است یا خیر؟ این مقدار را نیز در local storage ماندگار ذخیره می‌کنیم تا در صورت بستن مرورگر و مراجعه‌ی مجدد به آن، در دسترس باشد و به صورت خودکار پاک نشود.
    - متد setToken، بر اساس وضعیت rememberMe، مقادیر توکن‌های دریافتی از سرور را در local storage و یا session storage ذخیره می‌کند.
    - متد getRawAuthToken بر اساس یکی از مقادیر enum ارسالی به آن، مقدار خام access_token و یا refresh_token ذخیره شده را بازگشت می‌دهد.
    - متد deleteAuthTokens جهت حذف تمام توکن‌های ذخیره شده‌ی توسط برنامه استفاده خواهد شد. نمونه‌ی کاربرد آن در متد logout است.
    - متد setLoginSession پس از لاگین موفق فراخوانی می‌شود. کار آن ذخیره سازی توکن‌های دریافتی از سرور است. فرض آن نیز بر این است که خروجی json از طرف سرور، توکن‌ها را با کلیدهایی دقیقا مساوی access_token و refresh_token بازگشت می‌دهد:
     {"access_token":"...","refresh_token":"..."}
    اگر این کلیدها در برنامه‌ی شما نام دیگری را دارند، محل تغییر آن‌ها در فایل app.config.ts است.


    تکمیل متد ورود به سیستم

    در صفحه‌ی لاگین، کاربر نام کاربری، کلمه‌ی عبور و همچنین گزینه‌ی «به‌خاطر سپاری ورود» را باید تکمیل کند. به همین جهت اینترفیسی را برای این کار به نام Credentials در محل src\app\core\models\credentials.ts ایجاد می‌کنیم:
    export interface Credentials {
       username: string;
       password: string;
       rememberMe: boolean;
    }
    پس از آن در متد لاگین از این اطلاعات جهت دریافت توکن‌های دسترسی و به روز رسانی، استفاده خواهیم کرد:
    @Injectable()
    export class AuthService {
      constructor(
        @Inject(APP_CONFIG) private appConfig: IAppConfig,
        private http: HttpClient,
        private browserStorageService: BrowserStorageService   
      ) {
        this.updateStatusOnPageRefresh();
      }
    
      login(credentials: Credentials): Observable<boolean> {
        const headers = new HttpHeaders({ "Content-Type": "application/json" });
        return this.http
          .post(`${this.appConfig.apiEndpoint}/${this.appConfig.loginPath}`, credentials, { headers: headers })
          .map((response: any) => {
            this.browserStorageService.setLocal(this.rememberMeToken, credentials.rememberMe);
            if (!response) {
              this.authStatusSource.next(false);
              return false;
            }
            this.setLoginSession(response);
            this.authStatusSource.next(true);
            return true;
          })
          .catch((error: HttpErrorResponse) => Observable.throw(error));
      }
    }
    متد login یک Observable از نوع boolean را بازگشت می‌دهد. به این ترتیب می‌توان مشترک آن شد و در صورت دریافت true یا اعلام لاگین موفق، کاربر را به صفحه‌ای مشخص هدایت کرد.
    در اینجا نیاز است اطلاعات شیء Credentials را به مسیر http://localhost:5000/api/account/login ارسال کنیم. به همین جهت نیاز به سرویس IAppConfig تزریق شده‌ی در سازنده‌ی کلاس وجود دارد تا با دسترسی به this.appConfig.apiEndpoint، مسیر تنظیم شده‌ی در فایل src\app\core\services\app.config.ts را دریافت کنیم.
    پس از لاگین موفق:
    - ابتدا وضعیت rememberMe انتخاب شده‌ی توسط کاربر را در local storage مرورگر جهت مراجعات آتی ذخیره می‌کنیم.
    - سپس متد setLoginSession، توکن‌های دریافتی از شیء response را بر اساس وضعیت rememberMe در local storage ماندگار و یا session storage فرار، ذخیره می‌کند.
    - در آخر با فراخوانی متد next مربوط به authStatusSource با پارامتر true، به تمام کامپوننت‌های مشترک به این سرویس اعلام می‌کنیم که وضعیت لاگین موفق بوده‌است و اکنون می‌توانید نسبت به آن عکس العمل نشان دهید.


    تکمیل متد خروج از سیستم

    کار خروج، با فراخوانی متد logout صورت می‌گیرد:
    @Injectable()
    export class AuthService {
    
      constructor(
        @Inject(APP_CONFIG) private appConfig: IAppConfig,
        private http: HttpClient,
        private router: Router
      ) {
        this.updateStatusOnPageRefresh();
      }
    
      logout(navigateToHome: boolean): void {
        this.http
          .get(`${this.appConfig.apiEndpoint}/${this.appConfig.logoutPath}`)
          .finally(() => {
            this.deleteAuthTokens();
            this.unscheduleRefreshToken();
            this.authStatusSource.next(false);
            if (navigateToHome) {
              this.router.navigate(["/"]);
            }
          })
          .map(response => response || {})
          .catch((error: HttpErrorResponse) => Observable.throw(error))
          .subscribe(result => {
            console.log("logout", result);
          });
      }
    }
    در اینجا در ابتدا متد logout سمت سرور که در مسیر http://localhost:5000/api/account/logout قرار دارد فراخوانی می‌شود. پس از آن در پایان کار در متد finally (چه عملیات فراخوانی logout سمت سرور موفق باشد یا خیر)، ابتدا توسط متد deleteAuthTokens تمام توکن‌ها و اطلاعات ذخیره شده‌ی در مرورگر حذف می‌شوند. در ادامه با فراخوانی متد next مربوط به authStatusSource با مقدار false، به تمام مشترکین سرویس جاری اعلام می‌کنیم که اکنون وقت عکس العمل نشان دادن به خروجی سیستم و به روز رسانی رابط کاربری است. همچنین اگر پارامتر navigateToHome نیز مقدار دهی شده بود، کاربر را به صفحه‌ی اصلی برنامه هدایت می‌کنیم.


    اعتبارسنجی وضعیت لاگین و توکن‌های ذخیره شده‌ی در مرورگر

    برای اعتبارسنجی access token دریافتی از طرف سرور، نیاز به بسته‌ی jwt-decode است. به همین جهت دستور ذیل را در خط فرمان صادر کنید تا بسته‌ی آن به پروژه اضافه شود:
     > npm install jwt-decode --save
    در ادامه برای استفاده‌ی از آن، ابتدا بسته‌ی آن‌را import می‌کنیم:
     import * as jwt_decode from "jwt-decode";
    و سپس توسط متد jwt_decode آن می‌توان به اصل اطلاعات توکن دریافتی از طرف سرور، دسترسی یافت:
      getDecodedAccessToken(): any {
        return jwt_decode(this.getRawAuthToken(AuthTokenType.AccessToken));
      }
    این توکن خام، پس از decode، یک چنین فرمت نمونه‌ای را دارد که در آن، شماره‌ی کاربری (nameidentifier)، نام کاربری (name)، نام نمایشی کاربر (DisplayName)، نقش‌های او (قسمت role) و اطلاعات تاریخ انقضای توکن (خاصیت exp)، مشخص هستند:
    {
      "jti": "d1272eb5-1061-45bd-9209-3ccbc6ddcf0a",
      "iss": "http://localhost/",
      "iat": 1513070340,
      "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier": "1",
      "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name": "Vahid",
      "DisplayName": "وحید",
      "http://schemas.microsoft.com/ws/2008/06/identity/claims/serialnumber": "709b64868a1d4d108ee58369f5c3c1f3",
      "http://schemas.microsoft.com/ws/2008/06/identity/claims/userdata": "1",
      "http://schemas.microsoft.com/ws/2008/06/identity/claims/role": [
        "Admin",
        "User"
      ],
      "nbf": 1513070340,
      "exp": 1513070460,
      "aud": "Any"
    }
    برای مثال اگر خواستیم به خاصیت DisplayName این شیء decode شده دسترسی پیدا کنیم، می‌توان به صورت ذیل عمل کرد:
      getDisplayName(): string {
        return this.getDecodedAccessToken().DisplayName;
      }
    و یا خاصیت exp آن، بیانگر تاریخ انقضای توکن است. برای تبدیل آن به نوع Date، ابتدا باید به این خاصیت در توکن decode شده دسترسی یافت و سپس توسط متد setUTCSeconds آن‌را تبدیل به نوع Date کرد:
      getAccessTokenExpirationDateUtc(): Date {
        const decoded = this.getDecodedAccessToken();
        if (decoded.exp === undefined) {
          return null;
        }
        const date = new Date(0); // The 0 sets the date to the epoch
        date.setUTCSeconds(decoded.exp);
        return date;
      }
    اکنون که به این تاریخ انقضای توکن دسترسی یافتیم، می‌توان از آن جهت تعیین اعتبار توکن ذخیره شده‌ی در مرورگر، استفاده کرد:
      isAccessTokenTokenExpired(): boolean {
        const expirationDateUtc = this.getAccessTokenExpirationDateUtc();
        if (!expirationDateUtc) {
          return true;
        }
        return !(expirationDateUtc.valueOf() > new Date().valueOf());
      }
    و در آخر متد isLoggedIn که وضعیت لاگین بودن کاربر جاری را مشخص می‌کند، به صورت ذیل تعریف می‌شود:
      isLoggedIn(): boolean {
        const accessToken = this.getRawAuthToken(AuthTokenType.AccessToken);
        const refreshToken = this.getRawAuthToken(AuthTokenType.RefreshToken);
        const hasTokens = !this.isEmptyString(accessToken) && !this.isEmptyString(refreshToken);
        return hasTokens && !this.isAccessTokenTokenExpired();
      }
    
      private isEmptyString(value: string): boolean {
        return !value || 0 === value.length;
      }
    ابتدا بررسی می‌کنیم که آیا توکن‌های درخواست شده‌ی از کش مرورگر، وجود خارجی دارند یا خیر؟ پس از آن تاریخ انقضای access token را نیز بررسی می‌کنیم. تا همین اندازه جهت تعیین اعتبار این توکن‌ها در سمت کاربر کفایت می‌کنند. در سمت سرور نیز این توکن‌ها به صورت خودکار توسط برنامه تعیین اعتبار شده و امضای دیجیتال آن‌ها بررسی می‌شوند.

    در قسمت بعد، از این سرویس اعتبارسنجی تکمیل شده جهت ورود به سیستم و تکمیل کامپوننت header استفاده خواهیم کرد.


    کدهای کامل این سری را از اینجا می‌توانید دریافت کنید.
    برای اجرای آن فرض بر این است که پیشتر Angular CLI را نصب کرده‌اید. سپس از طریق خط فرمان به ریشه‌ی پروژه‌ی ASPNETCore2JwtAuthentication.AngularClient وارد شده و دستور npm install را صادر کنید تا وابستگی‌های آن دریافت و نصب شوند. در آخر با اجرای دستور ng serve -o، برنامه ساخته شده و در مرورگر پیش فرض سیستم نمایش داده خواهد شد (و یا همان اجرای فایل ng-serve.bat). همچنین باید به پوشه‌ی ASPNETCore2JwtAuthentication.WebApp نیز مراجعه کرده و فایل dotnet_run.bat را اجرا کنید، تا توکن سرور برنامه نیز فعال شود.
    مطالب
    آزمایش Web APIs توسط Postman - قسمت پنجم - انواع متغیرهای قابل تعریف در Postman
    در قسمت دوم، از متغیرهای سراسری، برای ایجاد یک گردش کاری بین دو درخواست ارسالی، استفاده کردیم. در این قسمت می‌خواهیم با انواع متغیرهای قابل تعریف در Postman آشنا شویم.


    متغیرهای سراسری در Postman

    برای تعریف متغیرهای سراسری که در تمام برگه‌های Postman قابل دسترسی باشند، می‌توان از متد pm.globals.set در قسمت Tests هر درخواست که پس از پایان درخواست جاری اجرا می‌شود، استفاده کرد. دراینجا فرصت خواهیم داشت تا مقدار دریافتی از سرور را در یک متغیر ذخیره کنیم. سپس می‌توان از این متغیر، در حین ارسال درخواستی دیگر، استفاده کرد که نمونه‌ای از آن‌را در قسمت دوم، با تبدیل شیء response به یک شیء جاوا اسکریپتی و استخراج خاصیت uuid آن، مشاهده کردید:
    let jsonResponse = pm.response.json();
    pm.globals.set("uuid", jsonResponse.uuid);
    روش دیگر تعریف متغیرهای سراسری، تعریف دستی آن‌ها است. در اینجا با می‌توان با کلیک بر روی دکمه‌‌ای با آیکن چشم، در بالای سمت راست صفحه، گزینه‌ی Edit مخصوص Global variables را انتخاب کرد:


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


    برای کار با متغیرهای سراسری، 4 متد زیر در Postman قابل استفاده هستند:

    عملکرد 
     متد
      تعریف و مقدار دهی یک متغیر سراسری 
     pm.globals.set("varName", "VALUE");
     دریافت مقدار یک متغیر سراسری 
    pm.globals.get("varName");
      پاک کردن یک متغیر سراسری مشخص 
    pm.globals.unset("varName");
     حذف تمام متغیرهای سراسری 
    pm.globals.clear();

    یکی از کاربردهای مهم متغیرهای سراسری، دریافت توکن‌های دسترسی پس از لاگین، در یک درخواست و استفاده‌ی از این توکن‌ها در درخواست‌های دیگر می‌باشد.


    روش استفاده‌ی از متغیرهای تعریف شده

    پس از تعریف این متغیرها، برای دسترسی به آن‌ها می‌توان از روش {{variableName}} در قسمت‌های مختلف postman استفاده کرد:
    Request URL: http://{{domain}}/users/{{userId}}
    Headers (key:value): X-{{myHeaderName}}:foo
    Request body: {"id": "{{userId}}", "name": "John Doe"}
    در اینجا سه مثال را در مورد امکان استفاده‌ی از متغیرها، در حین ساخت URLها، اجزای هدرها و یا بدنه‌ی درخواست ارسالی به سمت سرور، مشاهده می‌کنید.


    متغیرهای محیطی در Postman

    متغیرهای محیطی نیز بسیار شبیه به متغیرهای سراسری هستند، اما میدان دید آن‌ها کمتر است. برای مثال فرض کنید که قصد دارید اطلاعات پایه‌ی تنظیمات سرور و پورت آن‌ها را در بین درخواست‌های مختلف تغییر دهید؛ بدون اینکه بخواهید اصل درخواست‌ها تغییری کنند؛ یا اینکه قسمت تعریف متغیرهای سراسری بیش از حد شلوغ شده‌است و قصد دارید آن‌ها را گروه بندی کرده و مورد استفاده قرار دهید.

    در ابتدای کار، هیچ محیط خاصی تعریف نشده‌است:


    برای تعریف یک محیط جدید می‌توان بر روی دکمه‌‌ای با آیکن چشم، در بالای سمت راست صفحه و کلیک بر روی گزینه‌ی Add آن، یک محیط جدید را ایجاد کرد:


    در صفحه‌ی باز شده ابتدا باید نامی را برای این محیط جدید انتخاب کرد و سپس می‌توان key/valueهایی را مخصوص این محیط، تعریف نمود:

    پس از تعریف متغیرهای جدید محیطی و مقادیر آن‌ها، نحوه‌ی استفاده‌ی از این متغیرها دقیقا همانند روشی است که از متغیرهای سراسری استفاده کردیم و توسط روش {{variableName}} قابل دسترسی هستند.

    برای ویرایش اطلاعات منتسب به یک محیط، ابتدا باید آن‌را از dropdown محیط‌های بالای صفحه انتخاب کرد. اکنون با کلیک بر روی دکمه‌‌ای با آیکن چشم، در بالای سمت راست صفحه، لینک ویرایش این محیط انتخاب شده ظاهر می‌شود:



    API کار با متغیرهای محیطی از طریق کد نویسی

    API دسترسی به متغیرهای محیطی، بسیار شبیه به متغیرهای سراسری است:

      عملکرد   متد 
     تعریف و مقدار دهی یک متغیر محیطی 
    pm.environment.set("varName", "VALUE");
     دریافت مقدار یک متغیر محیطی 
    pm.environment.get("varName");
      پاک کردن یک متغیر محیطی مشخص 
    pm.environment.unset("varName");
     حذف تمام متغیرهای محیطی 
    pm.environment.clear();


     تفاوت میدان دید متغیرهای محیطی و متغیرهای سراسری

    باید دقت داشت که هر دوی متغیرهای سراسری و محیطی، در تمام برگه‌های تعریف شده قابل دسترسی می‌باشند و از این لحاظ تفاوتی بین آن‌ها نیست. اما فرض کنید یک متغیر سراسری را با نام port1 تعریف کرده‌اید و از آن برای ساخت آدرسی مانند https://localhost:{{port1}} استفاده کرده‌اید. همچنین دقیقا همین متغیر port1 را در محیط جدیدی به نام Env1 نیز تعریف کرده‌اید. اگر محیطی انتخاب نشده باشد، port1 به همان متغیر سراسری تعریف شده اشاره می‌کند.


    اما اگر محیط انتخابی را به Env1 تغییر دهیم، اینبار port1، از طریق اطلاعات Env1 تامین شده و مقدار متغیر سراسری تعریف شده را بازنویسی (یا مخفی) می‌کند. بنابراین در حین کارکردن با محیطی مشخص، متغیرهای محیطی، بر متغیرهای سراسری مقدم هستند.


    یک نکته: با نزدیک کردن اشاره‌گر ماوس، به یک متغیر تعریف شده‌ی در postman، می‌توان میدان دید آن‌را به سادگی مشاهده کرد و در این حالت دیگر جای حدس و گمانی باقی نمی‌ماند.


    عدم انتشار مقادیر اولیه‌ی حساس، در حین گرفتن خروجی‌ها

    اگر به تصاویر فوق دقت کنید، حین تنظیم مقادیر متغیرها، ستون اول، initial value نام دارد و ستون دوم، current value. هنگام گرفتن خروجی از یک مجموعه‌ی Postman، تنها این مقدار اولیه در خروجی وجود خواهد داشت و با دیگران به اشتراک گذاشته می‌شود. مقدار جاری همانی است که در حین ارسال درخواست‌ها مورد استفاده قرار می‌گیرد. بنابراین تنها کاربرد initial value، در تهیه‌ی خروجی‌ها است که در انتهای قسمت سوم آن‌را بررسی کردیم.
    مشکل اینجا است که اگر از متدهای به روز رسانی مقادیر متغیرها استفاده کنیم، هر دو مقدار را تغییر می‌دهند که ممکن است علاقمند نباشید آن‌ها را به اشتراک بگذارید. برای رفع این مشکل می‌توان به منوی  File->Settings آن مراجعه و گزینه‌ی Automatically persist variable values را خاموش کرد:


    با اینکار تغییر current value توسط متدهای API، سبب تغییر initial value که در exports ظاهر می‌شوند، نخواهد شد.