مطالب
بدست آوردن نام کشور و مشخصات کامل آن از روی آدرس IP

سایت‌های بسیاری هستند که سرویس‌هایی را برای بدست آوردن مشخصات کشور، از روی IP ارائه می‌دهند؛ ولی اکثر آنها برای این سرویسی که ارائه میدهند هزینه دریافت می‌کنند. سایتی که من در این مقاله معرفی خواهم کرد این سرویس را به رایگان ارائه می‌هد، به شرط اینکه درخواست‌های شما در هر ساعت بیشتر از 10000 نباشد. اگر این اتفاق روی دهد، یعنی درخواست‌های شما به بیش از 10000 در ساعت برسد، درخواست‌های شما با خطای HTTP 403, forbidden مواجه خواهد شد و تا زمانیکه محدودیت شما به پایان برسد، باید منتظر بمانید.

سرویسی که این سایت ارائه می‌دهد، سورس باز و رایگان میباشد. اگر شما با این محدودیت 10000 درخواست در ساعت مشکلی داشتید، می‌توانید این سرویس را از اینجا دریافت و آن‌را در سرور خودتان اجرا کنید. سورس سرویس هم بر روی github موجود هست.

API ارائه شده توسط این سایت، درخواست‌ها را بصورت زیر دریافت میکند:
freegeoip.net/{format}/{IP_or_hostname}
منظور از فرمت، قالبی است که پس از درخواست برای شما ارسال خواهد شد که به صورت‌های زیر خواهد بود:
freegeoip.net/xml/4.2.2.2 : XML
freegeoip.net/csv/8.8.8.8 :CSV
freegeoip.net/json/github.com :JSON
برای مثال اگر چنین درخواستی را ارسال کنیم:
http://freegeoip.net/xml/162.158.88.214
پاسخی که دریافت میکنیم بدین صورت خواهد بود:
<Response>
<IP>162.158.88.214</IP>
<CountryCode>DE</CountryCode>
<CountryName>Germany</CountryName>
<RegionCode>HE</RegionCode>
<RegionName>Hesse</RegionName>
<City>Frankfurt am Main</City>
<ZipCode>60438</ZipCode>
<TimeZone>Europe/Berlin</TimeZone>
<Latitude>50.1167</Latitude>
<Longitude>8.6833</Longitude>
<MetroCode>0</MetroCode>
</Response>
که در آن تمامی مشخصات مربوط به آن کشور، برای ما ارسال خواهد شد.

اگر آدرس IP را مشخص نکنید مشخصات مربوط به IP آدرس خودتان را دریافت خواهید کرد.


در ادامه از یک برنامه   console application برای دریافت این پاسخ  ارسال شده از سرویس و استخراج داده‌ها از آن استفاده خواهیم کرد.

البته این روش برای برنامه‌های تحت وب هم به همین صورت خواهد بود و تفاوتی نمی‌کند.

از این روش می‌توانید برای بلاک کردن کشورهایی که نمیخواهید به برنامه‌ی شما دسترسی پیدا کنند استفاده کنید و یا اینکه بدانید ip هایی که از سایت شما بازدید میکنند از کدام کشورهای جهان هستند و یا کار خلاقانه‌ای که می‌توانید انجام دهید، نمایش مکان‌های بازدید کننده‌ها بر روی نقشه گوگل، با استفاده از طول و عرض جغرافیایی که این سرویس در اختیارتان می‌گذارد‌، می‌باشد
 public static void ReadXmlElements(string ipAddress)
        {           
            XDocument xdoc=XDocument.Load("http://www.freegeoip.net/xml/" + ipAddress);
            var country = xdoc.Descendants("Response").Select(c => new
            {
                IpAddress = c.Element("IP")?.Value,
                CountryCode = c.Element("CountryCode")?.Value,
                CountryName = c.Element("CountryName")?.Value,
                RegionCode = c.Element("RegionCode")?.Value,
                RegionName = c.Element("RegionName")?.Value,
                City = c.Element("City")?.Value,
                ZipCode = c.Element("ZipCode")?.Value,
                TimeZone = c.Element("TimeZone")?.Value,
                Latitude = c.Element("Latitude")?.Value,
                Longitude = c.Element("Longitude")?.Value,
                MetroCode = c.Element("MetroCode")?.Value,
            });

             var countryData = country.First();
            Console.WriteLine("CountryName :" + countryData.CountryName 
                +Environment.NewLine + "CountryCode :"+countryData.CountryCode
                + Environment.NewLine + "RegionCode :"+countryData.RegionCode
                + Environment.NewLine + "RegionName :" + countryData.RegionName
                + Environment.NewLine + "City :" + countryData.City
                + Environment.NewLine + "ZipCode :" + countryData.ZipCode
                + Environment.NewLine + "TimeZone :" + countryData.TimeZone
                + Environment.NewLine + "Latitude :" + countryData.Latitude
                + Environment.NewLine + "Longitude :" + countryData.Longitude
                + Environment.NewLine + "MetroCode :" + countryData.MetroCode
                ); 
            
            Console.ReadKey();
        }

در متد main برنامه، متد ReadXmlElements را فراخوانی کرده و آدرس آی پی مورد نظر را پاس میدهیم:  

static void Main(string[] args)
        {
            ReadXmlElements("162.158.88.214");
        }
که نتیجه‌ی نمونه‌ای از آن‌را در تصویر ذیل می‌توانید مشاهده کنید


یک نکته : برای مقایسه کد کشور بدست آمده، باید لیست کاملی از کشورهای جهان را در اختیار داشته باشید، تا بتوانید تصمیم بگیرید که آیا کشور درخواست کننده، مد نظر شما هست یا خیر. برای این‌کار، کلاسی را آماده کرده‌ام که شامل کل کشورهای جهان میباشد و نام و کد کشور‌ها، در آن وجود دارد؛ به صورت زیر:

public class WorldCountries
    {
        public string CountryCode { get; set; }
        public string CountryName { get; set; }

    }

 public static class GenerateCountries
    {
        public static IList<WorldCountries> CreateCountries()
        {
            return new[]
                {
new WorldCountries { CountryCode = "AF", CountryName = "Afghanistan"},
new WorldCountries { CountryCode = "AX", CountryName = "Åland Islands"},
new WorldCountries { CountryCode = "AL", CountryName = "Albania"},
new WorldCountries { CountryCode = "DZ", CountryName = "Algeria"},
new WorldCountries { CountryCode = "AS", CountryName = "American Samoa"},
new WorldCountries { CountryCode = "AD", CountryName = "Andorra"},
new WorldCountries { CountryCode = "AO", CountryName = "Angola"},
new WorldCountries { CountryCode = "AI", CountryName = "Anguilla"},
new WorldCountries { CountryCode = "AQ", CountryName = "Antarctica"},
new WorldCountries { CountryCode = "AG", CountryName = "Antigua and Barbuda"},
new WorldCountries { CountryCode = "AR", CountryName = "Argentina"},
new WorldCountries { CountryCode = "AM", CountryName = "Armenia"},
new WorldCountries { CountryCode = "AW", CountryName = "Aruba"},
new WorldCountries { CountryCode = "AU", CountryName = "Australia"},
new WorldCountries { CountryCode = "AT", CountryName = "Austria"},
new WorldCountries { CountryCode = "AZ", CountryName = "Azerbaijan"},
new WorldCountries { CountryCode = "BS", CountryName = "Bahamas"},
new WorldCountries { CountryCode = "BH", CountryName = "Bahrain"},
new WorldCountries { CountryCode = "BD", CountryName = "Bangladesh"},
new WorldCountries { CountryCode = "BB", CountryName = "Barbados"},
new WorldCountries { CountryCode = "BY", CountryName = "Belarus"},
new WorldCountries { CountryCode = "BE", CountryName = "Belgium"},
new WorldCountries { CountryCode = "BZ", CountryName = "Belize"},
new WorldCountries { CountryCode = "BJ", CountryName = "Benin"},
new WorldCountries { CountryCode = "BM", CountryName = "Bermuda"},
new WorldCountries { CountryCode = "BT", CountryName = "Bhutan"},
new WorldCountries { CountryCode = "BA", CountryName = "Bosnia and Herzegovina"},
new WorldCountries { CountryCode = "BW", CountryName = "Botswana"},
new WorldCountries { CountryCode = "BV", CountryName = "Bouvet Island"},
new WorldCountries { CountryCode = "BR", CountryName = "Brazil"},
new WorldCountries { CountryCode = "IO", CountryName = "British Indian Ocean Territory"},
new WorldCountries { CountryCode = "BN", CountryName = "Brunei Darussalam"},
new WorldCountries { CountryCode = "BG", CountryName = "Bulgaria"},
new WorldCountries { CountryCode = "BF", CountryName = "Burkina Faso"},
new WorldCountries { CountryCode = "BI", CountryName = "Burundi"},
new WorldCountries { CountryCode = "KH", CountryName = "Cambodia"},
new WorldCountries { CountryCode = "CM", CountryName = "Cameroon"},
new WorldCountries { CountryCode = "CA", CountryName = "Canada"},
new WorldCountries { CountryCode = "CV", CountryName = "Cape Verde"},
new WorldCountries { CountryCode = "KY", CountryName = "Cayman Islands"},
new WorldCountries { CountryCode = "CF", CountryName = "Central African Republic"},
new WorldCountries { CountryCode = "TD", CountryName = "Chad"},
new WorldCountries { CountryCode = "CL", CountryName = "Chile"},
new WorldCountries { CountryCode = "CN", CountryName = "China"},
new WorldCountries { CountryCode = "CX", CountryName = "Christmas Island"},
new WorldCountries { CountryCode = "CC", CountryName = "Cocos (Keeling) Islands"},
new WorldCountries { CountryCode = "CO", CountryName = "Colombia"},
new WorldCountries { CountryCode = "KM", CountryName = "Comoros"},
new WorldCountries { CountryCode = "CG", CountryName = "Congo"},
new WorldCountries { CountryCode = "CK", CountryName = "Cook Islands"},
new WorldCountries { CountryCode = "CR", CountryName = "Costa Rica"},
new WorldCountries { CountryCode = "HR", CountryName = "Croatia"},
new WorldCountries { CountryCode = "CU", CountryName = "Cuba"},
new WorldCountries { CountryCode = "CW", CountryName = "Curaçao"},
new WorldCountries { CountryCode = "CY", CountryName = "Cyprus"},
new WorldCountries { CountryCode = "CZ", CountryName = "Czech Republic"},
new WorldCountries { CountryCode = "DK", CountryName = "Denmark"},
new WorldCountries { CountryCode = "DJ", CountryName = "Djibouti"},
new WorldCountries { CountryCode = "DM", CountryName = "Dominica"},
new WorldCountries { CountryCode = "DO", CountryName = "Dominican Republic"},
new WorldCountries { CountryCode = "EC", CountryName = "Ecuador"},
new WorldCountries { CountryCode = "EG", CountryName = "Egypt"},
new WorldCountries { CountryCode = "SV", CountryName = "El Salvador"},
new WorldCountries { CountryCode = "GQ", CountryName = "Equatorial Guinea"},
new WorldCountries { CountryCode = "ER", CountryName = "Eritrea"},
new WorldCountries { CountryCode = "EE", CountryName = "Estonia"},
new WorldCountries { CountryCode = "ET", CountryName = "Ethiopia"},
new WorldCountries { CountryCode = "FK", CountryName = "Falkland Islands (Malvinas)"},
new WorldCountries { CountryCode = "FO", CountryName = "Faroe Islands"},
new WorldCountries { CountryCode = "FJ", CountryName = "Fiji"},
new WorldCountries { CountryCode = "FI", CountryName = "Finland"},
new WorldCountries { CountryCode = "FR", CountryName = "France"},
new WorldCountries { CountryCode = "GF", CountryName = "French Guiana"},
new WorldCountries { CountryCode = "PF", CountryName = "French Polynesia"},
new WorldCountries { CountryCode = "TF", CountryName = "French Southern Territories"},
new WorldCountries { CountryCode = "GA", CountryName = "Gabon"},
new WorldCountries { CountryCode = "GM", CountryName = "Gambia"},
new WorldCountries { CountryCode = "GE", CountryName = "Georgia"},
new WorldCountries { CountryCode = "DE", CountryName = "Germany"},
new WorldCountries { CountryCode = "GH", CountryName = "Ghana"},
new WorldCountries { CountryCode = "GI", CountryName = "Gibraltar"},
new WorldCountries { CountryCode = "GR", CountryName = "Greece"},
new WorldCountries { CountryCode = "GL", CountryName = "Greenland"},
new WorldCountries { CountryCode = "GD", CountryName = "Grenada"},
new WorldCountries { CountryCode = "GP", CountryName = "Guadeloupe"},
new WorldCountries { CountryCode = "GU", CountryName = "Guam"},
new WorldCountries { CountryCode = "GT", CountryName = "Guatemala"},
new WorldCountries { CountryCode = "GG", CountryName = "Guernsey"},
new WorldCountries { CountryCode = "GN", CountryName = "Guinea"},
new WorldCountries { CountryCode = "GW", CountryName = "Guinea-Bissau"},
new WorldCountries { CountryCode = "GY", CountryName = "Guyana"},
new WorldCountries { CountryCode = "HT", CountryName = "Haiti"},
new WorldCountries { CountryCode = "HM", CountryName = "Heard Island and McDonald Islands"},
new WorldCountries { CountryCode = "VA", CountryName = "Holy See (Vatican City State)"},
new WorldCountries { CountryCode = "HN", CountryName = "Honduras"},
new WorldCountries { CountryCode = "HK", CountryName = "Hong Kong"},
new WorldCountries { CountryCode = "HU", CountryName = "Hungary"},
new WorldCountries { CountryCode = "IS", CountryName = "Iceland"},
new WorldCountries { CountryCode = "IN", CountryName = "India"},
new WorldCountries { CountryCode = "ID", CountryName = "Indonesia"},
new WorldCountries { CountryCode = "IR", CountryName = "Iran"},
new WorldCountries { CountryCode = "IQ", CountryName = "Iraq"},
new WorldCountries { CountryCode = "IE", CountryName = "Ireland"},
new WorldCountries { CountryCode = "IM", CountryName = "Isle of Man"},
new WorldCountries { CountryCode = "IL", CountryName = "Israel"},
new WorldCountries { CountryCode = "IT", CountryName = "Italy"},
new WorldCountries { CountryCode = "JM", CountryName = "Jamaica"},
new WorldCountries { CountryCode = "JP", CountryName = "Japan"},
new WorldCountries { CountryCode = "JE", CountryName = "Jersey"},
new WorldCountries { CountryCode = "JO", CountryName = "Jordan"},
new WorldCountries { CountryCode = "KZ", CountryName = "Kazakhstan"},
new WorldCountries { CountryCode = "KE", CountryName = "Kenya"},
new WorldCountries { CountryCode = "KI", CountryName = "Kiribati"},
new WorldCountries { CountryCode = "KP", CountryName = "Korea"},
new WorldCountries { CountryCode = "KW", CountryName = "Kuwait"},
new WorldCountries { CountryCode = "KG", CountryName = "Kyrgyzstan"},
new WorldCountries { CountryCode = "LV", CountryName = "Latvia"},
new WorldCountries { CountryCode = "LB", CountryName = "Lebanon"},
new WorldCountries { CountryCode = "LS", CountryName = "Lesotho"},
new WorldCountries { CountryCode = "LR", CountryName = "Liberia"},
new WorldCountries { CountryCode = "LY", CountryName = "Libya"},
new WorldCountries { CountryCode = "LI", CountryName = "Liechtenstein"},
new WorldCountries { CountryCode = "LT", CountryName = "Lithuania"},
new WorldCountries { CountryCode = "LU", CountryName = "Luxembourg"},
new WorldCountries { CountryCode = "MO", CountryName = "Macao"},
new WorldCountries { CountryCode = "MK", CountryName = "Macedonia"},
new WorldCountries { CountryCode = "MG", CountryName = "Madagascar"},
new WorldCountries { CountryCode = "MW", CountryName = "Malawi"},
new WorldCountries { CountryCode = "MY", CountryName = "Malaysia"},
new WorldCountries { CountryCode = "MV", CountryName = "Maldives"},
new WorldCountries { CountryCode = "ML", CountryName = "Mali"},
new WorldCountries { CountryCode = "MT", CountryName = "Malta"},
new WorldCountries { CountryCode = "MH", CountryName = "Marshall Islands"},
new WorldCountries { CountryCode = "MQ", CountryName = "Martinique"},
new WorldCountries { CountryCode = "MR", CountryName = "Mauritania"},
new WorldCountries { CountryCode = "MU", CountryName = "Mauritius"},
new WorldCountries { CountryCode = "YT", CountryName = "Mayotte"},
new WorldCountries { CountryCode = "MX", CountryName = "Mexico"},
new WorldCountries { CountryCode = "FM", CountryName = "Micronesia"},
new WorldCountries { CountryCode = "MD", CountryName = "Moldova"},
new WorldCountries { CountryCode = "MC", CountryName = "Monaco"},
new WorldCountries { CountryCode = "MN", CountryName = "Mongolia"},
new WorldCountries { CountryCode = "ME", CountryName = "Montenegro"},
new WorldCountries { CountryCode = "MS", CountryName = "Montserrat"},
new WorldCountries { CountryCode = "MA", CountryName = "Morocco"},
new WorldCountries { CountryCode = "MZ", CountryName = "Mozambique"},
new WorldCountries { CountryCode = "MM", CountryName = "Myanmar"},
new WorldCountries { CountryCode = "NA", CountryName = "Namibia"},
new WorldCountries { CountryCode = "NR", CountryName = "Nauru"},
new WorldCountries { CountryCode = "NP", CountryName = "Nepal"},
new WorldCountries { CountryCode = "NL", CountryName = "Netherlands"},
new WorldCountries { CountryCode = "NC", CountryName = "New Caledonia"},
new WorldCountries { CountryCode = "NZ", CountryName = "New Zealand"},
new WorldCountries { CountryCode = "NI", CountryName = "Nicaragua"},
new WorldCountries { CountryCode = "NE", CountryName = "Niger"},
new WorldCountries { CountryCode = "NG", CountryName = "Nigeria"},
new WorldCountries { CountryCode = "NU", CountryName = "Niue"},
new WorldCountries { CountryCode = "NF", CountryName = "Norfolk Island"},
new WorldCountries { CountryCode = "MP", CountryName = "Northern Mariana Islands"},
new WorldCountries { CountryCode = "NO", CountryName = "Norway"},
new WorldCountries { CountryCode = "OM", CountryName = "Oman"},
new WorldCountries { CountryCode = "PK", CountryName = "Pakistan"},
new WorldCountries { CountryCode = "PW", CountryName = "Palau"},
new WorldCountries { CountryCode = "PS", CountryName = "Palestine"},
new WorldCountries { CountryCode = "PA", CountryName = "Panama"},
new WorldCountries { CountryCode = "PG", CountryName = "Papua New Guinea"},
new WorldCountries { CountryCode = "PY", CountryName = "Paraguay"},
new WorldCountries { CountryCode = "PE", CountryName = "Peru"},
new WorldCountries { CountryCode = "PH", CountryName = "Philippines"},
new WorldCountries { CountryCode = "PN", CountryName = "Pitcairn"},
new WorldCountries { CountryCode = "PL", CountryName = "Poland"},
new WorldCountries { CountryCode = "PT", CountryName = "Portugal"},
new WorldCountries { CountryCode = "PR", CountryName = "Puerto Rico"},
new WorldCountries { CountryCode = "QA", CountryName = "Qatar"},
new WorldCountries { CountryCode = "RE", CountryName = "Réunion"},
new WorldCountries { CountryCode = "RO", CountryName = "Romania"},
new WorldCountries { CountryCode = "RU", CountryName = "Russian Federation"},
new WorldCountries { CountryCode = "RW", CountryName = "Rwanda"},
new WorldCountries { CountryCode = "BL", CountryName = "Saint Barthélemy"},
new WorldCountries { CountryCode = "KN", CountryName = "Saint Kitts and Nevis"},
new WorldCountries { CountryCode = "LC", CountryName = "Saint Lucia"},
new WorldCountries { CountryCode = "MF", CountryName = "Saint Martin (French part)"},
new WorldCountries { CountryCode = "PM", CountryName = "Saint Pierre and Miquelon"},
new WorldCountries { CountryCode = "VC", CountryName = "Saint Vincent and the Grenadines"},
new WorldCountries { CountryCode = "WS", CountryName = "Samoa"},
new WorldCountries { CountryCode = "SM", CountryName = "San Marino"},
new WorldCountries { CountryCode = "ST", CountryName = "Sao Tome and Principe"},
new WorldCountries { CountryCode = "SA", CountryName = "Saudi Arabia"},
new WorldCountries { CountryCode = "SN", CountryName = "Senegal"},
new WorldCountries { CountryCode = "RS", CountryName = "Serbia"},
new WorldCountries { CountryCode = "SC", CountryName = "Seychelles"},
new WorldCountries { CountryCode = "SL", CountryName = "Sierra Leone"},
new WorldCountries { CountryCode = "SG", CountryName = "Singapore"},
new WorldCountries { CountryCode = "SX", CountryName = "Sint Maarten (Dutch part)"},
new WorldCountries { CountryCode = "SK", CountryName = "Slovakia"},
new WorldCountries { CountryCode = "SI", CountryName = "Slovenia"},
new WorldCountries { CountryCode = "SB", CountryName = "Solomon Islands"},
new WorldCountries { CountryCode = "SO", CountryName = "Somalia"},
new WorldCountries { CountryCode = "ZA", CountryName = "South Africa"},
new WorldCountries { CountryCode = "GS", CountryName = "South Georgia and the South Sandwich Islands"},
new WorldCountries { CountryCode = "SS", CountryName = "South Sudan"},
new WorldCountries { CountryCode = "ES", CountryName = "Spain"},
new WorldCountries { CountryCode = "LK", CountryName = "Sri Lanka"},
new WorldCountries { CountryCode = "SD", CountryName = "Sudan"},
new WorldCountries { CountryCode = "SR", CountryName = "Suriname"},
new WorldCountries { CountryCode = "SJ", CountryName = "Svalbard and Jan Mayen"},
new WorldCountries { CountryCode = "SZ", CountryName = "Swaziland"},
new WorldCountries { CountryCode = "SE", CountryName = "Sweden"},
new WorldCountries { CountryCode = "CH", CountryName = "Switzerland"},
new WorldCountries { CountryCode = "SY", CountryName = "Syrian Arab Republic"},
new WorldCountries { CountryCode = "TW", CountryName = "Taiwan"},
new WorldCountries { CountryCode = "TJ", CountryName = "Tajikistan"},
new WorldCountries { CountryCode = "TZ", CountryName = "Tanzania"},
new WorldCountries { CountryCode = "TH", CountryName = "Thailand"},
new WorldCountries { CountryCode = "TL", CountryName = "Timor-Leste"},
new WorldCountries { CountryCode = "TG", CountryName = "Togo"},
new WorldCountries { CountryCode = "TK", CountryName = "Tokelau"},
new WorldCountries { CountryCode = "TO", CountryName = "Tonga"},
new WorldCountries { CountryCode = "TT", CountryName = "Trinidad and Tobago"},
new WorldCountries { CountryCode = "TN", CountryName = "Tunisia"},
new WorldCountries { CountryCode = "TR", CountryName = "Turkey"},
new WorldCountries { CountryCode = "TM", CountryName = "Turkmenistan"},
new WorldCountries { CountryCode = "TC", CountryName = "Turks and Caicos Islands"},
new WorldCountries { CountryCode = "TV", CountryName = "Tuvalu"},
new WorldCountries { CountryCode = "UG", CountryName = "Uganda"},
new WorldCountries { CountryCode = "UA", CountryName = "Ukraine"},
new WorldCountries { CountryCode = "AE", CountryName = "United Arab Emirates"},
new WorldCountries { CountryCode = "GB", CountryName = "United Kingdom"},
new WorldCountries { CountryCode = "US", CountryName = "United States"},
new WorldCountries { CountryCode = "UM", CountryName = "United States Minor Outlying Islands"},
new WorldCountries { CountryCode = "UY", CountryName = "Uruguay"},
new WorldCountries { CountryCode = "UZ", CountryName = "Uzbekistan"},
new WorldCountries { CountryCode = "VU", CountryName = "Vanuatu"},
new WorldCountries { CountryCode = "VE", CountryName = "Venezuela"},
new WorldCountries { CountryCode = "VN", CountryName = "Viet Nam"},
new WorldCountries { CountryCode = "VG", CountryName = "British Virgin Islands"},
new WorldCountries { CountryCode = "VI", CountryName = "US Virgin Islands"},
new WorldCountries { CountryCode = "WF", CountryName = "Wallis and Futuna"},
new WorldCountries { CountryCode = "EH", CountryName = "Western Sahara"},
new WorldCountries { CountryCode = "YE", CountryName = "Yemen"},
new WorldCountries { CountryCode = "ZM", CountryName = "Zambia"},
new WorldCountries { CountryCode = "ZW", CountryName = "Zimbabwe"},
                };
        }
    }

دریافت کد کامل این پروژه
مطالب
آناتومی یک گزارش خطای خوب
به مشکلی در برنامه‌ای برخورده‌اید؟ کتابخانه‌ای کار نمی‌کند؟ خطایی را دریافت کرده‌اید؟ برنامه کامپایل نمی‌شود؟ برنامه آنطور که مدنظر شما است رفتار نمی‌کند؟ برای طرح این مسایل، صرف عنوان کردن «برنامه کار نمی‌کنه» یا «خطا می‌ده» منزلت خودتان را تا حد یک کاربر عادی تازه کار تنزل داده‌اید. در ادامه ساختار یک گزارش خطای خوب را بررسی خواهیم کرد، تا شما را سریعتر به مقصودتان برساند و همچنین کار پیگیری برنامه نویس یا برنامه نویس‌های مسئول را نیز مقداری ساده‌تر کند.


کارهای لازم پیش از طرح سؤال
- سعی کنید انجمن‌های مرتبط را یکبار بررسی و جستجو کنید.
- عین خطای دریافتی را در گوگل جستجو کنید. اگر از برنامه‌ها یا کتابخانه‌های معروف و متداول استفاده می‌کنید، یکی از مزیت‌های مهم کار با آن‌ها، «تنها نبودن» است! یقین داشته باشید خطایی را که دریافت کرده‌اید پیشتر توسط ده‌ها نفر دیگر در سایت‌های مختلف مطرح شده‌اند و بالاخره با بررسی آن‌ها می‌توان به پاسخ رسید.
- شاید راهنمای برنامه در این مورد خاص مطلبی را عنوان کرده است.

و ... به صورت خلاصه باید بتوانید به این سؤال پاسخ دهید: «خودت چکار کردی؟». حداقل نشان دهید که فرد حاضر و آماده طلبی نیستید و پیشتر یک حداقل تقلایی را انجام داده‌اید.


کجا باید سؤال پرسید؟
- اگر به انجمنی برای طرح سؤال خود مراجعه کرده‌اید، حتما زیر شاخه صحیحی را انتخاب کنید تا سؤال شما بسته نشود یا کلا حذف نگردد. برای مثال سؤال ASP.NET را در بخش سی‌شارپ نپرسید یا برعکس یا اگر سایتی مقاله‌ای را منتشر کرده، ذیل آن در مورد نحوه بک آپ گرفتن از اکانت توئیتر خود سؤال نپرسید!
- اگر پاسخی را دریافت کردید، ادامه بحث را ذیل همان مطلب پیگیری کنید و مجددا مطلب جدیدی را ایجاد نکنید.
- اگر تا نیم ساعت بعد جوابی را دریافت نکردید، کل بخش‌های یک سایت را با ارسال پیام خود اسپم نکنید. یکبار ارسال یک سؤال کافی است. اکثر این سایت‌ها حالت یک «چت آفلاین» را دارند. به این معنا که ابتدا پیغام خود را می‌گذارید، اگر مدتی بعد (ممکن است چند ساعت بعد) شخصی آن‌را مشاهده کرد و قادر به پاسخ دهی بود، به شما کمک خواهد کرد. بنابراین اگر سریعا به جواب نرسیدید، نه کل سایت را اسپم کنید و نه ... شروع به رفتارهای ناشایست کنید. اینکار با فریاد کشیدن وسط یک جمع تفاوتی ندارد. اشخاص مرتبط همواره آنلاین نیستند؛ ضمنا ممکن است واقعا پاسخی برای یک سؤال نداشته باشند. منصف باشید.
- از ایمیل‌های خصوصی افراد یا قسمت پیام‌های خصوصی سایت‌ها برای ارسال سؤالات شخصی استفاده نکنید. ایمیل خصوصی، مخصوص کارهای شخصی است. قسمت پیام‌های خصوصی یک سایت عموما مخصوص رسیدگی به مشکلات کاربری است. این تصور را نداشته باشید که اشخاص مشاور شخصی رایگان پروژه‌های تجاری شما هستند.
- بهترین محل برای پرسیدن سؤالات مرتبط با یک پروژه خاص، mailing list یا انجمن گفتگو و یا issue tracker آن پروژه است. وقت خودتان را با ارسال خطاهای یک پروژه خاص، در یک انجمن عمومی و همه منظوره تلف نکنید. کمی جستجو کنید که سایت اصلی پروژه کجا است. بعد دقت کنید آیا جایی برای پرسش و پاسخ دارد یا خیر. اکثر پروژه‌های خوب، مکانی را جهت جمع آوری بازخوردهای پروژه خود، اختصاص می‌دهند.


چطور باید سؤال پرسید؟
سؤال فنی خوب پرسیدن هم یک هنر است؛ که تعدادی از مشخصه‌های مهم آن‌را در ذیل مرور خواهیم کرد:
- عنوان مناسبی را برای سؤال خود انتخاب کنید. «لطفا کمک کنید» یا «من مشکل دارم» یا «مشکل در پروژه»، عموما واکنش‌های تندی را به همراه دارند؛ و تا حد ارسال اسپم در یک سایت بی‌کیفیت تلقی می‌شوند. ضمن اینکه انتخاب عنوان‌های مناسب، جستجوهای بعدی را در سایت ساده می‌کنند و کمک بزرگی خواهند بود به افراد بعدی.
- محیطی را که خطا در آن رخ داده است، توضیح دهید. ذکر IIS تنها کافی نیست. کدام نگارش آن؟ در کدام ویندوز؟
برای مثال شماره نگارش کتابخانه یا نرم افزار مورد استفاده را ذکر کنید. شاید خطایی که گرفته‌اید در نگارش بعدی آن برطرف شده است.
ذکر شماره نگارش VS.NET یا شماره نگارش دات نت مورد استفاده، سیستم عامل و کلا توصیف محیط بروز خطا، عموما بسیار مفید هستند.
- حتما کل خطای دریافت شده را ارسال کنید. اگر در یک برنامه C خطایی حاصل شود، احتمالا شکلی مانند Error 0xABCD را دارد. اما استثناءهای دات نت به همراه stack trace و حتی شماره سطر خطای حاصل نیز هستند. همین مساله می‌تواند به خطایابی نهایی بسیار کمک کند.
- سؤال خود را طوری مطرح کنید که شخص مقابل بتواند آن‌را در کمترین زمان ممکن «باز تولید» کند. برای مثال ذکر خطای دریافتی بسیار خوب است. اگر داده‌ای که سبب بروز این خطا شده است را هم ارسال کنید، مفید‌تر خواهد بود؛ یا اگر دستور پاور شل خاصی در کنسول نیوگت خطا می‌دهد، صرفا عنوان نکنید که جواب نگرفته‌اید. چه دستوری را اجرا کرده‌اید؟ چه خطایی را دریافت کرده‌اید؟ ساختار پروژه شما چیست؟ آیا شخص مقابل می‌تواند بر اساس اطلاعاتی که ارائه دادید یک آزمایش شخصی را تدارک ببیند؟ آیا می‌تواند آن‌را با توضیحات شما مجددا تولید کند؟
زمان باز تولید خطا را هم مدنظر داشته باشید. برای مثال اگر بتوانید قطعه کدی را ارائه دهید که در کمترین زمان ممکن، صرفا با کپی و پیست آن در VS.NET قابل کامپایل باشد، بسیاری علاقمند به پاسخگویی به شما خواهند شد. در غیراینصورت آنچنان انتظار نداشته باشید که شخص پاسخ دهنده وقت زیادی را برای رسیدگی به جزئیات سؤال شما صرف کند؛ یا مدتی مشغول به تهیه یک مثال جدید بر مبنای توضیحات شما شود.
حجم کدهای ارسالی شما نیز در اینجا مهم هستند. کل پروژه خود را ارسال نکنید! سعی کنید یک مثال کوچک را که بتواند سریعا خطای مدنظر شما را بازتولید کند، ارسال کنید و نه بیشتر. همچنین کدهایی که برای اجرا نیاز به GUI نداشته باشند نیز در این حالت اولویت دارند.
و به صورت خلاصه، خودتان را بجای پاسخ دهنده قرار دهید. آیا با چند جمله‌ای که ارائه داده‌اید، می‌توان انتظار پاسخی را داشت یا خیر.
- ایمیل شخصی خود را در انتهای پیام ارسال نکنید. کسی اهمیتی نمی‌دهد! اگر سؤال شما پاسخی داشته باشد، همانجا دریافت خواهید کرد و نه در میل باکس شخصی.
- املاء و انشای متنی را که ارسال می‌کنید، یکبار بررسی کنید. اگر برای شما اهمیتی ندارد که چه کلمات و جمله بندی را باید بکار برد، برای شخص مقابل هم آنچنان اهمیتی نخواهد داشت که زیاد وقت صرف کند.
- از بکار بردن smileyهای بیش از حد یا قرار دادن تعداد علامت تعجب‌های بیش از حد خودداری کنید. این موارد عموما به مسخره کردن شخص مقابل تفسیر می‌شوند.
- در بدو امر فریاد نکشید که «باگ» پیدا کرده‌اید؛ خصوصا اگر به mailing list اختصاصی یک پروژه پیامی را ارسال می‌کنید. چون اگر مشکل شما واقعا باگ نباشد، بیشتر یک توهین تلقی خواهد شد و در دفعات بعدی پاسخ دادن به شما به صورت ضمنی مؤثر خواهند بود؛ یا جواب نمی‌گیرید و یا جدی گرفته نخواهید شد.  
- هدف از کاری را که مشغول به انجام آن بود‌ه‌اید را نیز ذکر کنید. ذکر خطای دریافتی بسیار مفید است اما اگر بتوانید یک دید کلی را نسبت به کاری که مشغول به آن بوده‌اید، ایجاد کنید، شاید پاسخ بهتری را دریافت کنید. برای مثال جهت رسیدن به هدف و مقصود شما بهتر است از روش دیگری استفاده کنید.
- پس از اینکه پیامی را دریافت کردید، یک حداقل واکنشی را ارسال کنید. مثلا خوب بود؛ کمک کرد و یا مفید نبود. همین واکنش‌ها در آینده به کمک نتایج جستجوهای انجام شده خواهند آمد و اشخاص بعدی حداقل خواهند دانست که پاسخ داده شده صحیح بوده است یا خیر.

و همیشه بخاطر داشته باشید: تمام خدماتی که سایت‌های عمومی به شما ارائه می‌دهند «یک لطف» است و حقی را برای شما ایجاد نمی‌کنند. این اشخاص از شما پول نمی‌گیرند تا به سؤالات شما پاسخ دهند یا تبدیل به مشاور خصوصی رایگان شما شوند. می‌توانید محیط را برای این اشخاص، با اندکی احترام، ملایمت و انصاف، دلپذیرتر کنید.
مطالب
خواندنی‌های 5 اردیبهشت

  • - پیش نمایش MySQL 5.4 توسط شرکت سان ارائه شد. این شرکت مدعی است که response times آن 90 درصد نسبت به نگارش قبلی سریعتر شده (+ و +)
  • - سایت GeoCities بسته شد. سایت Google pages هم قرار است تا یکی دو ماه دیگر بسته شود (به عبارت دیگر شکل و شمایل این وبلاگ در آن تاریخ کلا به هم خواهد ریخت چون فایل‌های سایت را در آن‌جا هاست کرده‌ام ... به دریا هم که برویم ...)
اشتراک‌ها
رویداد آنلاین رونمایی از معماری رابط کاربری جدید همکاران سیستم

معماری رابط کاربری جدید همکاران سیستم یک پیاده‌سازی از سبک معماری Micro Frontends، مبتنی بر آخرین تکنولوژی‌های این حوزه است و این امکان را فراهم می‌کند که ماژول‌های ارائه شده توسط تیم‌های توسعه نرم‌افزار در عین استقلال بتوانند به شکل یکپارچه عمل کنند و در شرایطی که با هم تعامل استاندارد برقرار می‌کنند، همدیگر را گسترش دهند. فلسفه ایجاد، تاریخچه تصمیم‌گیری، تکنولوژی‌ها و تکنیک‌های مرتبط، شیوه‌ی پیاده‌سازی، تست، انتشار، تجربه کاربر و دیزاین سیستم از موضوعاتی است که در این رویداد به آن‌ها می‌پردازیم.

محورهای اصلی رویداد:

  • دغدغه‌های معماری در اپلیکیشن‌هایی با مقیاس بزرگ
  • معماری رابط کاربری جدید همکاران سیستم
  • به کارگیری انگولار در Micro Frontends
  • تجربه کاربری و دیزاین سیستم
  • تست خودکار در اکوسیستم جدید 

 📌 زمان برگزاری: 9 بهمن 1399 ، ساعت 10 تا 13

رویداد آنلاین رونمایی از معماری رابط کاربری جدید همکاران سیستم
مطالب
React 16x - قسمت 27 - احراز هویت و اعتبارسنجی کاربران - بخش 2 - استخراج و نمایش اطلاعات JWT و خروج از سیستم
در قسمت قبل، در هر دو حالت ثبت نام یک کاربر جدید و همچنین ورود به سیستم، یک JSON Web Token را از سرور دریافت کرده و در local storage مرورگر، ذخیره کردیم. اکنون قصد داریم محتوای این توکن را استخراج کرده و از آن جهت نمایش اطلاعات کاربر وارد شده‌ی به سیستم، استفاده کنیم. همچنین کار بهبود کیفیت کدهایی را هم که تاکنون پیاده سازی کردیم، انجام خواهیم داد.


نگاهی به محتوای JSON Web Token تولیدی

اگر مطلب قسمت قبل را پیگیری کرده باشید، پس از لاگین، یک چنین خروجی را در کنسول توسعه دهندگان مرورگر می‌توان مشاهده کرد که همان return Ok(new { access_token = jwt }) دریافتی از سمت سرور است:


اکنون این رشته‌ی طولانی را در حافظه کپی کرده و سپس به سایت https://jwt.io/#debugger-io مراجعه و در قسمت دیباگر آن، این رشته‌ی طولانی را paste می‌کنیم تا آن‌را decode کند:


برای نمونه payload آن حاوی یک چنین اطلاعاتی است:
{
  "jti": "b2921057-32a4-fbb2-0c18-5889c1ab8e70",
  "iss": "https://localhost:5001/",
  "iat": 1576402824,
  "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier": "1",
  "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name": "Vahid N.",
  "DisplayName": "Vahid N.",
  "http://schemas.microsoft.com/ws/2008/06/identity/claims/userdata": "1",
  "http://schemas.microsoft.com/ws/2008/06/identity/claims/role": "Admin",
  "nbf": 1576402824,
  "exp": 1576402944,
  "aud": "Any"
}
در اینجا یک‌سری از اطلاعات کاربر، مانند id ، name ، DisplayName و یا حتی role او درج شده‌است؛ به همراه تاریخ صدور (iat) و انقضای (exp) این token که به صورت Unix time format بیان می‌شوند. به هر کدام از این خواصی که در اینجا ذکر شده‌اند، یک user claim گفته می‌شود. به عبارتی، این token ادعا می‌کند (claims) که نقش کاربر وارد شده‌ی به سیستم، Admin است. برای بررسی صحت این ادعا نیز یک امضای دیجیتال (مشخص شده‌ی با رنگ آبی) را به همراه این توکن سه قسمتی (قسمت‌های مختلف آن، با 2 نقطه از هم جدا شده‌اند که در تصویر نیز با سه رنگ متمایز، مشخص است)، ارائه کرده‌است. به این معنا که اگر قسمتی از اطلاعات این توکن، در سمت کاربر دستکاری شود، دیگر در سمت سرور تعیین اعتبار مجدد نخواهد شد؛ چون نیاز به یک امضای دیجیتال جدید را دارد که کلیدهای خصوصی تولید آن، تنها در سمت سرور مهیا هستند و به سمت کلاینت ارسال نمی‌شوند.


استخراج اطلاعات کاربر وارد شده‌ی به سیستم، از JSON Web Token دریافتی

همانطور که در payload توکن دریافتی از سرور نیز مشخص است، اطلاعات ارزشمندی از کاربر، به همراه آن ارائه شده‌اند و مزیت کار با آن، عدم نیاز به کوئری گرفتن مداوم از سرور و بانک اطلاعاتی، جهت دریافت مجدد این اطلاعات است. بنابراین اکنون در برنامه‌ی React خود، قصد داریم مشابه کاری را که سایت jwt.io انجام می‌دهد، پیاده سازی کرده و به این اطلاعات دسترسی پیدا کنیم و برای مثال DisplayName را در Navbar نمایش دهیم. برای این منظور فایل app.js را گشوده و تغییرات زیر را به آن اعمال می‌کنیم:
- می‌خواهیم اطلاعات کاربر جاری را در state کامپوننت مرکزی App قرار دهیم. سپس زمانیکه کار رندر کامپوننت NavBar درج شده‌ی در متد رندر آن فرا می‌رسد، می‌توان این اطلاعات کاربر را به صورت props به آن ارسال کرد؛ و یا به هر کامپوننت دیگری در component tree برنامه.
- بنابراین ابتدا کامپوننت تابعی بدون حالت App را تبدیل به یک کلاس کامپوننت استاندارد مشتق شده‌ی از کلاس پایه‌ی Component می‌کنیم. اکنون می‌توان state را نیز به آن اضافه کرد:
class App extends Component {
  state = {};
- سپس متد componentDidMount را به این کامپوننت اضافه می‌کنیم؛ در آن ابتدا token ذخیره شده‌ی در local storage را دریافت کرده و سپس decode می‌کنیم تا payload اطلاعات کاربر وارد شده‌ی به سیستم را استخراج کنیم. در آخر state را توسط این اطلاعات به روز می‌کنیم.
- برای decode کردن توکن، نیاز به نصب کتابخانه‌ی زیر را داریم:
> npm install --save jwt-decode
- پس از نصب آن، ابتدا امکانات آن‌را import کرده و سپس از آن در متد componentDidMount استفاده می‌کنیم:
import jwtDecode from "jwt-decode";
// ...

class App extends Component {
  state = {};

  componentDidMount() {
    try {
      const jwt = localStorage.getItem("token");
      const currentUser = jwtDecode(jwt);
      console.log("currentUser", currentUser);
      this.setState({ currentUser });
    } catch (ex) {
      console.log(ex);
    }
  }
ابتدا آیتمی با کلید token از localStorage استخراج می‌شود. سپس توسط متد jwtDecode، تبدیل به یک شیء حاوی اطلاعات کاربر جاری وارد شده‌ی به سیستم گشته و در آخر در state درج می‌شود. در اینجا درج try/catch ضروری است؛ از این جهت که متد jwtDecode، در صورت برخورد به توکنی غیرمعتبر، یک استثناء را صادر می‌کند و این استثناء نباید بارگذاری برنامه را با اخلال مواجه کند. از این جهت که اگر توکنی غیرمعتبر است (و یا حتی در localStorage وجود خارجی ندارد؛ برای کاربران لاگین نشده)، کاربر باید مجددا برای دریافت نمونه‌ی معتبر آن، لاگین کند.

- اکنون می‌توان شیء currentUser را به صورت props، به کامپوننت NavBar ارسال کرد:
  render() {
    return (
      <React.Fragment>
        <ToastContainer />
        <NavBar user={this.state.currentUser} />
        <main className="container">


نمایش اطلاعات کاربر وارد شده‌ی به سیستم در NavBar

پس از ارسال شیء کاربر به صورت props به کامپوننت src\components\navBar.jsx، کدهای این کامپوننت را به صورت زیر جهت نمایش نام کاربر جاری وارد شده‌ی به سیستم تغییر می‌دهیم:
const NavBar = ({ user }) => {
چون این کامپوننت به صورت یک کامپوننت تابعی بدون حالت تعریف شده، برای دریافت props می‌توان یا آن‌را به صورت مستقیم به عنوان پارامتر تعریف کرد و یا خواص مدنظر را با استفاده از Object Destructuring به عنوان پارامتر دریافت نمود.
سپس می‌توان لینک‌های Login و Register را به صورت شرطی رندر کرد و نمایش داد:
{!user && (
  <React.Fragment>
    <NavLink className="nav-item nav-link" to="/login">
      Login
    </NavLink>
    <NavLink className="nav-item nav-link" to="/register">
      Register
    </NavLink>
  </React.Fragment>
)}
در اینجا اگر شیء user تعریف شده باشد (یعنی کاربر، توکن ذخیره شده‌ای در local storage داشته باشد)، دیگر لینک‌های login و register نمایش داده نمی‌شوند. به علاوه برای اعمال && به چند المان React، نیاز است آن‌ها را داخل یک والد، مانند React.Fragment محصور کرد.

شبیه به همین حالت را برای هنگامیکه کاربر، تعریف شده‌است، جهت نمایش نام او و لینک به Logout، نیاز داریم:
{user && (
  <React.Fragment>
    <NavLink className="nav-item nav-link" to="/logout">
      Logout
    </NavLink>
    <NavLink className="nav-item nav-link" to="/profile">
      {user.DisplayName}
    </NavLink>
  </React.Fragment>
)}
user.DisplayName درج شده‌ی در اینجا، اطلاعات خودش را از payload توکن decode شده‌ی دریافتی از سرور، تامین می‌کند؛ با این خروجی:


فعلا تا پیش از پیاده سازی Logout، برای آزمایش آن، به کنسول توسعه دهندگان مرورگر مراجعه کرده و توکن ذخیره شده‌ی در ذیل قسمت application->storage را دستی حذف کنید. سپس صفحه را ریفرش کنید. اینبار لینک‌های به Login و Register نمایان می‌شوند.
یک مشکل! در این حالت (زمانیکه توکن حذف شده‌است)، از طریق قسمت Login به برنامه وارد شوید. هرچند این قسمت‌ها به درستی کار خود را انجام می‌دهند، اما هنوز در منوی بالای سایت، نام کاربری و لینک به Logout ظاهر نشده‌اند. علت اینجا است که در کامپوننت App، کار دریافت توکن در متد componentDidMount انجام می‌شود و این متد نیز تنها یکبار در طول عمر برنامه فراخوانی می‌شود. برای رفع این مشکل به src\components\loginForm.jsx مراجعه کرده و بجای استفاده از history.push برای هدایت کاربر به صفحه‌ی اصلی برنامه، نیاز خواهیم داشت تا کل برنامه را بارگذاری مجدد کنیم. یعنی بجای:
this.props.history.push("/");
باید از سطر زیر استفاده کرد:
window.location = "/";
این سطر سبب full page reload برنامه شده و در نتیجه متد componentDidMount کامپوننت App، یکبار دیگر فراخوانی خواهد شد. شبیه به همین کار را در کامپوننت src\components\registerForm.jsx نیز باید انجام داد.


پیاده سازی Logout کاربر وارد شده‌ی به سیستم

برای logout کاربر تنها کافی است توکن او را از local storage حذف کنیم. به همین جهت مسیریابی جدید logout را که به صورت لینکی به NavBar اضافه کردیم:
<NavLink className="nav-item nav-link" to="/logout">
   Logout
</NavLink>
به فایل src\App.js اضافه می‌کنیم.
import Logout from "./components/logout";
// ...

class App extends Component {
  render() {
    return (
          // ...
          <Switch>
            // ...
            <Route path="/logout" component={Logout} />
البته برای اینکار نیاز است کامپوننت جدید src\components\logout.jsx را با محتوای زیر ایجاد کنیم:
import { Component } from "react";

class Logout extends Component {
  componentDidMount() {
    localStorage.removeItem("token");
    window.location = "/";
  }

  render() {
    return null;
  }
}

export default Logout;
که در متد componentDidMount آن، کار حذف توکن ذخیره شده‌ی در localStorage انجام شده و سپس کاربر را با یک full page reload، به ریشه‌ی سایت هدایت می‌کنیم.


بهبود کیفیت کدهای نوشته شده

اگر به کامپوننت App دقت کنید، کلید token استفاده شده‌ی در آن، در چندین قسمت برنامه مانند login و logout، تکرار و پراکنده شده‌است. بنابراین بهتر است جزئیات پیاده سازی مرتبط با اعتبارسنجی کاربران، به ماژول مختص به آن‌ها (src\services\authService.js) منتقل شود تا سایر قسمت‌های برنامه، به صورت یک‌دستی از آن استفاده کنند و اگر در این بین نیاز به تغییری بود، فقط یک ماژول نیاز به تغییر، داشته باشد.
برای این منظور، ابتدا متد login قبلی را طوری تغییر می‌دهیم که کار ذخیره سازی توکن را نیز در authService.js انجام دهد:
const tokenKey = "token";

export async function login(email, password) {
  const {
    data: { access_token }
  } = await http.post(apiEndpoint + "/login", { email, password });
  console.log("JWT", access_token);
  localStorage.setItem(tokenKey, access_token);
}
سپس متد doSumbit کامپوننت src\components\loginForm.jsx، به صورت زیر ساده می‌شود:
const { data } = this.state;
await auth.login(data.username, data.password);
window.location = "/";

همین‌کار را برای logout نیز در authService انجام داده:
export function logout() {
  localStorage.removeItem(tokenKey);
}
و در ادامه متد componentDidMount کامپوننت Logout را برای استفاده‌ی از آن، اصلاح می‌کنیم:
import * as auth from "../services/authService";

class Logout extends Component {
  componentDidMount() {
    auth.logout();

منطق دریافت اطلاعات کاربر جاری نیز باید به authService منتقل شود؛ چون مسئولیت دریافت توکن و سپس decode آن، نباید به کامپوننت App واگذار شود:
import jwtDecode from "jwt-decode";
//...

export function getCurrentUser() {
  try {
    const jwt = localStorage.getItem(tokenKey);
    const currentUser = jwtDecode(jwt);
    console.log("currentUser", currentUser);
    return currentUser;
  } catch (ex) {
    console.log(ex);
    return null;
  }
}
سپس متد componentDidMount کامپوننت App، به صورت زیر خلاصه خواهد شد:
import * as auth from "./services/authService";

class App extends Component {
  state = {};

  componentDidMount() {
    const currentUser = auth.getCurrentUser();
    this.setState({ currentUser });
  }

جای دیگری که از localStorage استفاده شده، متد doSumbit کامپوننت ثبت نام کاربران است. این قسمت را نیز به صورت زیر به authService اضافه می‌کنیم:
export function loginWithJwt(jwt) {
   localStorage.setItem(tokenKey, jwt);
}
سپس ابتدای متد doSumbit را برای استفاده‌ی از آن به صورت زیر تغییر می‌دهیم:
import * as auth from "../services/authService";
// ...
const response = await userService.register(this.state.data);
auth.loginWithJwt(response.headers["x-auth-token"]);


کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید: sample-27-backend.zip و sample-27-frontend.zip
مطالب
نکته‌ای تکمیلی در مورد مجوز استفاده از iTextSharp

یکی از سؤ برداشت‌های متداول از کارهای سورس باز موجود این است:
«من مجازم از این کتابخانه‌ی سورس باز هرجایی و هر طوری که دوست دارم استفاده کنم.»

در کل این یک «توهم» بزرگ است. بسته به مجوز پروژه (^)، جمله‌ی فوق می‌تواند صحیح یا کاملا نادرست باشد.
برای نمونه من خیلی‌ها رو می‌بینم که می‌گن: «از MySQL استفاده کن که رایگانه». نه دوست عزیز؛ اشتباه می‌کنید! فقط برای کارهای سورس باز رایگان است. مجوز نگارش Community و رایگان آن در رده‌ی مجوز‌های GPL است (^). به این معنا که اگر روزی مطابق قوانین کپی رایت قرار شد رفتار شود، به سراغ کار سورس بسته شما که دارد از MySQL رایگان استفاده می‌کند، خواهند آمد. جهت اطلاع!
به همین جهت کسانی که کار تجاری سورس بسته انجام می‌دهند از طرف کتابخانه‌های دارای مجوز GPL حتی رد هم نمی‌شوند؛ چه برسد به اینکه بخواهند آزادانه از آن استفاده کنند.

در مورد مجوز کتابخانه‌ی iTextSharp پیشتر مطلبی را در این سایت خوانده‌اید:
مجوز این کتابخانه، GNU Affero General Public License است. به این معنا که شما موظفید، تغییری در قسمت تهیه کننده خواص فایل PDF تولیدی که به صورت خودکار به نام کتابخانه تنظیم می‌شود، ندهید. اگر می‌خواهید این قسمت را تغییر دهید باید هزینه کنید. همچنین با توجه به اینکه این مجوز، GPL است یعنی زمانیکه از آن استفاده کردید باید کار خود را به صورت سورس باز ارائه دهید (^).

و ... نکته تکمیلی مهم اینکه:
این کتابخانه تا نگارش 4.1.7 تحت مجوز MPL/LGPL ارائه شده و «بدون مشکل» در کارهای تجاری سورس بسته قابل استفاده است. از نگارش 5 به بعد، AGPL شده و برای کارهای تجاری سورس بسته «رایگان نیست» (^).
برای نمونه سورس نسخه 4.1.7 از این آدرس قابل دریافت است.
این سورس را از پروژه "FDFToolkit .NET" اینجا نقل کردم چون تهیه کننده این پروژه دقیقا به این مطلب اشاره کرده و کار خود را به نگارش 4.1.7 کتابخانه iTextSharp عمدا محدود کرده است.

مطالب
Microsoft Test Manager - قسمت سوم

در کنار کاربرگ contents کاربرگی با نام  Propertiesوجود دارد که می‌توانید یک سری تنظیمات را برای plan خود انجام دهید. این تنظیمات از قبیل تغییر عنوان plan، تعیین مسیر پروژه، تاریخ شروع و پایان، کاربری که مالک این plan است، وضعیت جاری تست‌های plan و تعیین مرورگر و ویندوز نیز می‌باشد که می‌توانید در تصویر زیر آن را مشاهده کنید. 

اگر در لیست کشویی مربوط به test settings مقدار <default> قرار داشت می‌توانید با انتخاب آیتم new از لیست settings جدیدی را ایجاد نمایید و یا می‌توانید لیست test settings هایی را که قبلا ایجاد کرده اید انتخاب نمایید و برای ویرایش آن با کلیک بر روی لینک open که کنار لیست قرار دارد، می‌توانید تنظیمات را ویرایش نمایید.

همانطور که در تصویر بالا مشاهده می‌کنید، در سمت چپ، بخش هایی برای انجام تنظیمات مربوط به تست وجود دارد. در قسمت general تنظیماتی از قبیل عنوان test settings، شرح و نوع اجرای دستی یا اتومات بودن تستتان وجود دارد. در بخش roles می‌توانید نقش هایی را برای این تست انتخاب نمایید و در قسمت data and diagnostics می‌توانید یک سری اطلاعاتی را که می‌خواهید در زمان تست دریافت کنید، انتخاب کنید. برای اطلاعات بیشتر در مورد این بخش می‌توانید در سایت مایکروسافت مطالعه کنید.

حالا بر می‌گردیم به بخش contents و موارد تست خود را می‌سازیم. همانطور که در تصویر پایین مشاهده می‌کنید در بخش contents و در سمت راست پنجره یک گزینه ای به نام configuration وجود دارد.

در configuration شما می‌توانید یک سری تنظیمات مربوط به test شما است انجام دهید مثلا نوع مرورگری که می‌خواهید تست خود را اجرا کنید و یا اولویت تست را مشخص نمایید یا حتی نوع سیستم عامل را مشخص کنید. هم چنین می‌توانید چندین configuration تعریف کنید و از هر کدام برای یک test suite استفاده کنید. به صورت پیش فرض test suite از تنظیمات config والد خودش یعنی test plan استفاده می‌کند.

دوباره برمی گردیم به بخش contents و می‌خواهیم یک test suite با استفاده از add requirements بسازیم. همانطور که در بخش‌های قبل توضیح دادم می‌توانیم به چند روش test suite بسازیم که یکی از آن‌ها همین add requirements بود که می‌توانستید از test suite هایی که قبلا ساخته اید به این پروژه تستتان اضافه کنید.

با انتخاب گزینه add requirements پنجره ای باز می‌شود که می‌توانید همه test suite‌ها را مشاهده کنید و حتی می‌توانید براساس عنوان و یا وضعیت تست و ... فیلتر کنید.

بعد از اینکه در قسمت بالا کوئری خود را تنظیم کردید با انتخاب گزینه run می‌توانید کوئری خود را اجرا کرده و لیست test suite‌ها را براساس آن کوئری فیلتر کنید. می‌توانید یک یا چند سطر را انتخاب کرده و با زدن دکمه add requirements to plan آن‌ها را به plan خود اضافه نمایید. حالا ما یک test suite با استفاده از test suite هایی که قبلا ساخته ایم ایجاد کردیم. حالا باید مورد تست‌های مان را به این test suite اضافه کنیم. در سمت راست با کلیک بر روی گزینه add پنجره ای مشابه پنجره بالا باز می‌شود که شما می‌توانید test case‌ها را فیلتر کنید و یک یا چند مورد را انتخاب کرده و با زدن دکمه add test cases آن‌ها را به test suite تان اضافه کنید. برای اضافه کردن مورد تست جدید هم می‌توانید با کلیک بر روی new که در کنار گزینه Add قرار دارد مورد تست جدیدی را بسازید.

در تصویر زیر می‌توانید بخش‌های مختلف تست را که در بخش‌های قبل هم توضیح دادم ببینید.

نظرات مطالب
ASP.NET Web API - قسمت اول
روش‌های زیادی برای تامین امنیت در وب API و کار با «کاربران شناسایی شده» وجود دارند. لیست رسمی
از این لیست رسمی، دو مورد معروف آن در سایت جاری بررسی شده:
ASP.NET Identity
Forms authentication
مباحث پایه‌ای این‌ها مشترک است بین MVC و وب فرم‌ها و سایر فناوری‌های مشابه.
مطالب
چک لیست تهیه یک گزارش خوب

مشخصات یک گزارش خوب عموما به شرح زیر است:

1- باید هر سطر گزارش شماره ردیف داشته باشد. (باید امکان ارجاع به هر سطر در صورت بروز مشکل میسر باشد)
2- باید در هر صفحه، شماره صفحه و تعداد کل صفحات ذکر شود. (اگر چاپ شد بر این اساس بتوان ارتباط بین صفحات را یافت)
3- در هر صفحه باید تاریخ و ساعت روز تهیه گزارش حتما ذکر شود. (بعدا جهت رفع اختلافات لازم می‌شود. مثلا می‌گویند این عدد اشتباه است. اما واقعا این عدد در زمان تهیه گزارش درست بوده، اما الان بر اساس اطلاعات جدید ... بله ... چیزی دیگری است، یا به قول آن‌ها اشتباه است)
4- در پایان هر صفحه، یک سری از ستون‌های عددی باید جمع کل داشته باشد.
5- در ابتدای هر صفحه باید "نقل از صفحه قبل" یا همان سطر جمع کل صفحه قبل ذکر شود.
6- هدر گزارش باید در تمام صفحات تکرار شود. (باید مشخص باشد این صفحه گزارش که الان به دست من رسیده متعلق به کجاست، عنوانش چیست حداقل؟)
7- سر ستون‌ها هم باید در هر صفحه تکرار شوند. (مثلا الان صفحه 20 یک گزارش پیش روی شما است. باید بدانید معنای این ستون سوم ظاهر شده در گزارش چیست)
8- تمام اعداد موجود در گزارش باید جداکننده سه رقمی داشته باشند. (خواندن 4446327531 ساده‌تر است یا خواندن 4,446,327,531 ؟)
9- تمام اعداد گزارش باید فارسی نمایش داده شوند. (این مورد را می‌شود با فونت‌های دستکاری شده که احتمالا شما هم یک دوجین از آن‌ها را دارید، حل کرد. فونت‌هایی که با یک فونت ادیتور مثل برنامه معروف FontCreator ویرایش شده و بجای اعداد انگلیسی آن‌ها، همان اعداد فارسی قرار گرفته‌اند)

مطالب
دو نکته کوتاه در مورد RSS های فارسی

اولین نکته مربوط به تاریخ هر مدخل (entry) می‌شود. این تاریخ نباید شمسی باشد! این تاریخ باید حتما استاندارد باشد. عموما یکی از دو استاندارد زیر باید مورد استفاده قرار گیرد:


RFC #822
http://www.ietf.org/rfc/rfc0822.txt
Standard for ARPA Internet Text Messages (Date and Time Specification)

RFC #3339
http://www.ietf.org/rfc/rfc3339.txt
Date and Time on the Internet (Timestamps)


برای مثال در دات نت برای تولید این فرمت استاندارد می‌توان به صورت زیر عمل کرد:
DateTime.Now.ToUniversalTime().ToString("r")

متاسفانه بسیاری از برنامه نویس‌های هم وطن این نکته را رعایت نمی‌کنند و برنامه‌های فیدخوان را دچار مشکل می‌کنند. (برای نمونه برنامه معروف feedDemon تاریخ چند سال پیش را ثبت خواهد کرد و در به روز رسانی و دنبال کردن مطالب سایت مورد نظر دچار مشکل خواهد شد)

چند مثال از این دست: (سورس صفحه را در مرورگر مطالعه نمائید)
http://www.faradade.com/Xml/RSS.xml
و یا
http://www.ayande.ir/atom.xml
و یا
http://www.tci-sk.ir/Rss.aspx
(ایشان بهتر است علاوه بر این مورد، از XmlTextWriter استفاده کنند و خروجی را به صورت یک فایل xml و نه html در مرورگر Flush کنند)

و یا بدتر از این بعضی از سایت‌ها آموزش‌های غلطی را هم ارائه می‌دهند:
http://www.faradade.com/Article.aspx?code=a726ae6a-f8e1-4b29-88b4-8e7a04e6d06d
به قسمت pubDate دقت کنید.
مطابق معمول این آموزش الان در 200 سایت کپی و پیست شده! عنوان آموزش را در گوگل جستجو کنید!
این کد آموزش داده شده یک ایراد دیگر هم دارد. آیا الزامی دارد که حتما قسمت con.Close به همین ترتیب نوشته شده اجرا شود؟ اگر این بین خطایی رخ دهد تکلیف این کانکشن باز و سایر موارد چه خواهد شد؟ کلا استفاده از try و finally و یا استفاده از using را برای چه هدفی اختراع کرده‌اند؟

و یا بعضی از سایت‌ها این مورد را رعایت می‌کنند اما به صورت نصفه و نیمه. برای مثال: (تاریخ ارائه شده کامل نیست. بنابراین استاندارد تلقی نخواهد شد)
http://www.srco.ir/Articles/RSSArticles.xml

برای آزمایش میزان استاندارد بودن خروجی فید خود می‌توان از سرویس زیر استفاده کرد:
http://validator.w3.org/feed/

مطلب دیگر ایراد نیست بلکه نکته‌ای است که حداقل از IE7 به بعد رعایت می‌شود:
لطفا زبان فید را مشخص کنید! بله، اگر این مورد را مشخص کنید، از IE7 به بعد فید فارسی به صورت خودکار از راست به چپ نمایش داده می‌شود و این امر سبب سهولت خواندن مطالب فارسی سایت شما خواهد شد.
مشاهده اصل مطلب که توسط یکی از اعضای تیم مربوطه مایکروسافت نوشته شده:
مشاهده

اصلاحیه برای RSS فارسی:
<language>fa-IR</language>

و برای Atom فارسی:
<feed xml:lang="fa">

و اگر می‌خواهید خروجی استانداردی داشته باشید، کتابخانه سورس باز زیر توصیه می‌شود:
http://www.codeplex.com/Argotic

با تشکر از همکاری شما!