دو سوال از شما داشتم:
1- خروجی ios چطور؟
2- با دلفی دات نت مینویسید؟
البته من در Xamarin تست نکردم ولی از آنجای که Xamarin طبق مستندات گفته شده، برنامه را به نتیجه نهایی Compile میکند نباید تفاوات چندانی باشد ولی برای اندروید چون کمپایل درجا صورت میگیرد لاجرم باید dllها فرستاده شوند.
همچنین مزیت دیگر آن، انتقال سادهتر کدهای جاوا به سیشارپ است؛ از این لحاظ که ویژگی مشابهی در زبان جاوا تحت عنوان «Default Methods» سالها است که وجود دارد.
یک مثال از ویژگی «پیاده سازیهای پیشفرض در اینترفیسها»
interface ILogger { void Log(string message); } class ConsoleLogger : ILogger { public void Log(string message) { Console.WriteLine(message); } }
مدتی بعد بر اساس نیازمندیهای مشخصی به این نتیجه خواهید رسید که بهتر است overload دیگری را برای متد Log در اینترفیس ILogger، درنظر بگیریم. مشکلی که این تغییر به همراه دارد، کامپایل نشدن کلاس ConsoleLogger در یک برنامهی ثالث است و این کلاس باید الزاما این overload جدید را پیاده سازی کند؛ در غیراینصورت قادر به کامپایل برنامهی خود نخواهد شد. اکنون در C# 8.0 میتوان برای این نوع تغییرات، در همان اینترفیس اصلی، یک پیاده سازی پیشفرض را نیز قرار داد:
interface ILogger { void Log(string message); void Log(Exception exception) => Console.WriteLine(exception); }
ویژگی «پیاده سازیهای پیشفرض در اینترفیسها» چگونه پیاده سازی شدهاست؟
واقعیت این است که امکان پیاده سازی این ویژگی، سالها است که در سطح کدهای IL دات نت وجود داشته (از زمان دات نت 2) و اکنون از طریق کدهای برنامه با بهبود کامپایلر آن، قابل دسترسی شدهاست.
تاثیر زمینهی کاری بر روی دسترسی به پیاده سازیهای پیشفرض
مثال زیر را درنظر بگیرید:
interface IDeveloper { void LearnNewLanguage(string language, DateTime dueDate); void LearnNewLanguage(string language) { // default implementation LearnNewLanguage(language, DateTime.Now.AddMonths(6)); } } class BackendDev : IDeveloper // compiles OK { public void LearnNewLanguage(string language, DateTime dueDate) { // Learning new language... } }
سؤال: به نظر شما اکنون کدامیک از کاربردهای زیر از کلاس BackendDev، کامپایل میشود و کدامیک خیر؟
IDeveloper dev1 = new BackendDev(); dev1.LearnNewLanguage("Rust"); var dev2 = new BackendDev(); dev2.LearnNewLanguage("Rust");
There is no argument given that corresponds to the required formal parameter 'dueDate' of 'BackendDev.LearnNewLanguage(string, DateTime)' (CS7036) [ConsoleApp]
ارثبری چندگانه چطور؟
احتمالا حدس زدهاید که این قابلیت ممکن است ارثبری چندگانه را که در سیشارپ ممنوع است، میسر کند. تا C# 8.0، یک کلاس تنها از یک کلاس دیگر میتواند مشتق شود؛ اما این محدودیت در مورد اینترفیسها وجود ندارد. به علاوه تاکنون اینترفیسها مانند کلاسها، امکان تعریف پیاده سازی خاصی را نداشتند و صرفا یک قرارداد بیشتر نبودند. بنابراین اکنون این سؤال مطرح میشود که آیا میتوان با ارائهی پیاده سازی پیشفرض متدها در اینترفیسها، ارثبری چندگانه را در سیشارپ پیاده سازی کرد؛ مانند مثال زیر؟!
using System; namespace ConsoleApp { public interface IDev { void LearnNewLanguage(string language) => Console.Write($"Learning {language} in a default way."); } public interface IBackendDev : IDev { void LearnNewLanguage(string language) => Console.Write($"Learning {language} in a backend way."); } public interface IFrontendDev : IDev { void LearnNewLanguage(string language) => Console.Write($"Learning {language} in a frontend way."); } public interface IFullStackDev : IBackendDev, IFrontendDev { } public class Dev : IFullStackDev { } }
IFullStackDev dev = new Dev(); dev.LearnNewLanguage("TypeScript");
The call is ambiguous between the following methods or properties: 'IBackendDev.LearnNewLanguage(string)' and 'IFrontendDev.LearnNewLanguage(string)' (CS0121)
تفاوت امکانات کلاسهای Abstract با متدهای پیشفرض اینترفیسها چیست؟
اینترفیسها هنوز نمیتوانند مانند کلاسها، سازندهای را تعریف کنند. نمیتوانند متغیرها/فیلدهایی را در سطح اینترفیس داشته باشند. همچنین در اینترفیسها همهچیز public است و امکان تعریف سطح دسترسی دیگری وجود ندارد.
بنابراین باید بخاطر داشت که هدف از تعریف اینترفیسها، ارائهی «یک رفتار» است و هدف از تعریف کلاسها، ارائه «یک حالت».
یک نکته: در نگارشهای پیش از C# 8.0 هم میتوان ویژگی «متدهای پیشفرض» را شبیه سازی کرد
واقعیت این است که توسط ویژگی «متدهای الحاقی»، سالها است که امکان افزودن «متدهای پیشفرضی» به اینترفیسها در زبان سیشارپ وجود دارد:
namespace MyNamespace { public interface IMyInterface { IList<int> Values { get; set; } } public static class MyInterfaceExtensions { public static int CountGreaterThan(this IMyInterface myInterface, int threshold) { return myInterface.Values?.Where(p => p > threshold).Count() ?? 0; } } }
var myImplementation = new MyInterfaceImplementation(); // Note that there's no typecast to IMyInterface required var countGreaterThanFive = myImplementation.CountGreaterThan(5);
یکی دیگر از روشهایی که جهت بهبود کیفیت کدها مورد استفاده قرار میگیرد، «طراحی با قراردادها» است؛ به این معنا که «بهتر است» متدهای تعریف شده پیش از استفاده از آرگومانهای خود، آنها را دقیقا بررسی کنند و به این نوع پیش شرطها، قرارداد هم گفته میشود.
نمونهای از آنرا در قسمت 9 مشاهده کردید که در آن اگر آرگومانهای متد AddRole، خالی یا نال باشند، یک استثناء صادر میشود. این نوع پیغامهای واضح و دقیق در مورد عدم اعتبار ورودیهای دریافتی، بهتر است از پیغامهای کلی و نامفهوم null reference exception که بدون بررسی stack trace و سایر ملاحظات، علت بروز آنها مشخص نمیشوند.
در دات نت 4، جهت سهولت این نوع بررسیها، مفهوم Code Contracts ارائه شده است. (این نام هم از این جهت بکارگرفته شده که Design by Contract نام تجاری شرکت ثبت شدهای در آمریکا است!)
یک مثال:
متد زیر را در نظر بگیرید. اگر divisor مساوی صفر باشد، استثنای کلی DivideByZeroException صادر میشود:
namespace Refactoring.Day10.DesignByContract.Before
{
public class MathMehods
{
public double Divide(int dividend, int divisor)
{
return dividend / divisor;
}
}
}
روش متداول «طراحی با قراردادها» جهت بهبود کیفیت کد فوق پیش از دات نت 4 به صورت زیر است:
using System;
namespace Refactoring.Day10.DesignByContract.After
{
public class MathMehods
{
public double Divide(int dividend, int divisor)
{
if (divisor == 0)
throw new ArgumentException("divisor cannot be zero", "divisor");
return dividend / divisor;
}
}
}
در اینجا پس از بررسی آرگومان divisor، قرارداد خود را به آن اعمال خواهیم کرد. همچنین در استثنای تعریف شده، پیغام واضحتری به همراه نام آرگومان مورد نظر، ذکر شده است که از هر لحاظ نسبت به استثنای استاندارد و کلی DivideByZeroException مفهومتر است.
در دات نت 4 ، به کمک امکانات مهیای در فضای نام System.Diagnostics.Contracts، این نوع بررسیها نام و امکانات درخور خود را یافتهاند:
using System.Diagnostics.Contracts;
namespace Refactoring.Day10.DesignByContract.After
{
public class MathMehods
{
public double Divide(int dividend, int divisor)
{
Contract.Requires(divisor != 0, "divisor cannot be zero");
return dividend / divisor;
}
}
}
البته اگر قطعه کد فوق را به همراه divisor=0 اجرا کنید، هیچ پیغام خاصی را مشاهده نخواهید کرد؛ از این لحاظ که نیاز است تا فایلهای مرتبط با آنرا از این آدرس دریافت و نصب کنید. این کتابخانه با VS2008 و VS2010 سازگار است. پس از آن، برگهی Code contracts به عنوان یکی از برگههای خواص پروژه در دسترس خواهد بود و به کمک آن میتوان مشخص کرد که برنامه حین رسیدن به این نوع بررسیها چه عکس العملی را باید بروز دهد.
برای مطالعه بیشتر:
خواندنیهای 19 تیر
امنیت
توسعه وب
دات نت فریم ورک
دبلیو سی اف
دبلیو پی اف و سیلور لایت
متفرقه
محیطهای مجتمع توسعه
مرورگرها
مسایل انسانی، اجتماعی و مدیریتی برنامه نویسی
ویندوز
پی اچ پی
گوگل اجازهی فعال کردن POP3 را روی اکانتها GMail میدهد. فرض کنید با استفاده از یکی از کلاینتهای POP3 دات نت میخواهیم ایمیلها را با برنامه نویسی دریافت کنیم (و مثلا از Outlook استفاده نکنیم). اکنون به نظر شما عنوان دریافت شده زیر چه معنایی دارد؟
=?UTF-8?B?QW5hbHl0aWNzIHZhaGlkbmFzaXJpLmJsb2dzcG90LmNvbSAyMDA4MTIyNiAo2KLZhdin?= =?UTF-8?B?2LEg2LPYp9mK2Kop?=
برای درک اتفاق رخ داده باید به RFC های مربوطه مراجعه کرد (RFC-2822 و RFC-2047). مطابق استانداردهای ذکر شده، هدر ارسالی یک ایمیل همواره باید از حروف اسکی تشکیل شود. حال اگر عنوان ایمیل که جزئی از هدر را تشکیل میدهد از حروف غیر اسکی تشکیل شد، حتما باید یک لایه encoding روی آنها صورت گیرد. دو حالت تعریف شده در اینجا مطابق استاندارد میسر است:
الف) Quoted Printable : در این حالت عنوان با =?utf-8?Q شروع میشود.
ب) Base64 : در این روش عنوان با =?utf-8?B شروع خواهد شد.
روش متداول، روش ب است که نسبت به روش الف فشردهتر میباشد. در این حالت برای درک معنای قسمتهای مختلف رشته دریافت شده باید به الگوی زیر مراجعه کرد:
=?charset?encoding?EncodedText?=
در رشته طولانی فوق که در ابتدای مقاله به آن اشاره شده، عنوان به دو قسمت تجزیه شده. یا به عبارتی دوبار الگوی فوق در آن تکرار شده است که باید EncodedText های آنها را یافت و سپس آنها را با توجه به charset مربوطه از حالت base64 به یک رشته معمولی تبدیل نمود.
//using System.Text;
public static string Base64ToString(string charset, string encodedString)
{
//تبدیل بیس 64 به آرایهای از بایتها
byte[] buffer = Convert.FromBase64String(encodedString);
//تبدیل آرایهای از بایتها به رشته با توجه به انکدینگ مربوطه
return Encoding.GetEncoding(charset).GetString(buffer);
}
string subject = Base64ToString("utf-8", "QW5hbHl0aWNzIHZhaGlkbmFzaXJpLmJsb2dzcG90LmNvbSAyMDA4MTIyNiAo2KLZhdin2LEg2LPYp9mK2Kop");
اعتبارسنجی در Angular 2 توسط JWT
public static void Register(HttpConfiguration config) { var cors = new EnableCorsAttribute("*", "*", "*"); config.EnableCors(cors); config.Formatters.JsonFormatter.SupportedMediaTypes .Add(new MediaTypeHeaderValue("application/json")); }
Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at http://localhost:13748/. This can be fixed by moving the resource to the same domain or enabling CORS.
آغاز فصل سوم:
در فصل گذشته در مورد بسته بندی و توزیع اسمبلیها، بررسیهایی را انجام دادیم. در این نوع توزیع، فرض ما بر این بود که دسترسی به اسمبلیها، از طریق دایرکتوری خود اپلیکیشن میباشد؛ ولی برای اسمبلیهای عمومی، صحبتی به میان نیاوردیم. در این فصل، ما تمرکز خود را برای توزیع اسمبلیهای عمومی میگذاریم. اسمبلیهای عمومی این قابلیت را میدهند که از طریق چند اپلیکیشن قابل دسترسی باشند. سادهترین و قابل دسترسترین نمونهی این اسمبلیها، اسمبلیهای خود دات نت فریم ورک هستند؛ یا نمونهی دیگر، شرکتهای ثالثی مثل تلریک، که برای استفادهی دیگر برنامه نویسان اسمبلی میسازند.
مشکلی که در توزیع اسمبلیهای عمومی وجود دارد این است که شما باید این اطمینان را کسب کنید که اسمبلی شما، همیشه همان اسمبلی خواهد بود و تغییری در آن رخ نخواهد داد. فرض کنید که شما از یک اسمبلی که توسط شرکت تلریک تهیه شده است استفاده کردهاید و برنامهی شما به خوبی با آن کار میکند. ولی چه اتفاقی میافتد که اگر برنامهی دیگری بعد از شما نصب شود و از همان اسمبلی، منتها از نسخهی دیگر آن استفاده میکند؟ بله برنامهی شما احتمال زیادی دارد که در این حالت به مشکل بر بخورد یا اینکه شخص دیگری یک اسمبلی دیگری همنام اسمبلی و هم نسخهی اسمبلی شما تولید میکند. برای رفع این مشکلات مایکروسافت تمهیداتی را اندیشیده است که ما به آن میگوییم «اسمبلی با نام قوی Strong Name Assembly».
اسمبلیها به دو دسته تقسیم میشوند: اسمبلیهای با نام قوی و اسمبلی
هایی با نام ضعیف ( این مورد در مستندات مایکروسافت نیست و توسط نویسندهی کتاب، این
اصطلاح ایجاد شده است).
در قسمت دوم گفتیم که اسمبلیها از قسمتهایی چون جداول مانیفست، هدرها، متادیتاها و ... تشکیل میشوند. اسمبلیهای نام قوی هم به همین شکل هستند. فقط توسط جفت کلید عمومی و خصوصی محافظت و امضا میشوند که برای ناشر، یک کلید منحصر به فرد را ایجاد میکنند و به ناشر این اطمینان را میدهند که اگر جفت کلیدی را که در دست شما است، به کسی ندهید، هیچ کس دیگری نمیتواند اسمبلی را با مشخصات اسمبلی شما امضاء کند.
حال یک اسمبلی نام قوی، دارای چهار خصوصیت است: نام اسمبلی بدون پسوند، نگارش، فرهنگ (Culture) و کلید عمومی.
از آنجا که خود کلید عمومی بسیار بزرگ میباشد، ما برای استفادهی راحتتر، از توکن کلید عمومی استفاده میکنیم که طول کمتری دارد. توکن کلید عمومی، یک مقدار هش شده است که از کلید عمومی به دست میآید:
"MyTypes, Version=1.0.8123.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" "MyTypes, Version=1.0.8123.0, Culture="enUS", PublicKeyToken=b77a5c561934e089" "MyTypes, Version=2.0.1234.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" "MyTypes, Version=1.0.8123.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"
استفاده از فضای نام
System.Reflection.AssemblyName
به شما اجازهی ساخت و دریافت اطلاعاتی را از اسمبلیها میدهد؛ هر نوع اطلاعاتی را که شامل 4 خصوصیت بالا میشود، به شما میدهد.
از آنجا که مطالب مربوطه به امضاء کردن اسمبلی، در سایت جاری موجود میباشند، این مباحث را میتوانید از طریق این مقاله "نام قوی" دنبال کنید تا در این باره گزافه گویی نکرده باشیم .
تصویر زیر توضیح بند بالا را نشان میدهد:
مختصری در مورد GAC
%SystemRoot%\Microsoft.NET\Assembly
هر اسمبلی را که توزیع میکنید، حتما حداقل به یک اسمبلی نام قوی ارجاع خواهد داشت؛ دلیل این گفته هم وجود فضای نام system.object در اسمبلی mscorlib است. برای همین، موقعیکه شما با csc، کامپایل میکنید، از سوئیچ reference استفاده میشود تا ارجاعی را به نام اسمبلی مورد نظر داشته باشد. اگر نام اسمبلی به طور کامل به همراه مسیر ذکر شود که مستقیما از همانجا فراخوانی میشود؛ در غیر این صورت مسیرهای زیر مورد بررسی قرار میگیرند:
- مسیر پوشهی کاری برنامه
- مسیر کامپایلر CSC
- هر مسیری که با سوئیچ lib به کامپایلر معرفی کرده باشید.
- هر مسیری که با متغیرهای Lib Environment مشخص شده باشند.
/reference:System.Drawing.dll
در ضمن اسمبلیهای موجود در مسیر کامپایلر به هیچ عنوان اهمیتی به نوع ماشین نمیدهند؛ چون متادیتاهای اسمبلی آنها اهمیت دارند نه کد IL آنها. کد IL فقط در زمان اجرا، برای ما اهمیت دارد و در زمان کامپایل، همان متادیتا کفایت میکند و در نهایت برنامه موقع اجرا، با توجه به نوع ماشین x86,x36,ARM، اسمبلی مورد نیاز خود را از طریق GAC فراهم میکند که هر یک از اسمبلیهای مخصوص هر ماشین، توسط GAC، در داخل زیر شاخههای مخصوص خود قرار گرفتهاند.
1- با استفاده از کلاس ApplicationUser، شیء ClaimsPrincipal را تولید میکند.
2- به این ClaimsPrincipal اطلاعاتی مانند ApplicationUser.Id و SecurityStamp اضافه میشوند.
3- در ادامه، ClaimsPrincipal به OWIN و کلاس CookieAuthenticationHandler آن ارسال میشود.
4- کار کلاس CookieAuthenticationHandler، تولید و تنظیم اطلاعاتی مانند تاریخ صدور کوکی، تاریخ انقضای آن، نوع کوکی، مانند ماندگار بودن یا امن بودن (HTTPS) و امثال آن است. حاصل این مراحله، تولید یک AuthenticationTicket است.
5- در آخر، AuthenticationTicket و ClaimsPrincipal به کلاس SecureDataFormat، برای ابتدا، serialize شدن اشیاء، رمزنگاری و در نهایت تبدیل آنها به فرمت base64، ارسال میشوند.
جزئیات تکمیلی مرحلهی آخر آن نیز به این ترتیب است:
AuthenticationTicket با استفاده از کلاس TicketSerializer سریالایز میشود. پس از آن یک memory stream تشکیل شده و اطلاعات ClaimsIdentity و AuthenticationTicket سریالایز شده به آن ارسال میشوند. این memory stream با استفاده از الگوریتم GZip فشرده شده و برای پردازش بیشتر بازگشت داده میشود. مرحلهی بعد، رمزنگاری اطلاعات فشرده سازی شدهاست. برای این منظور از کلاس DpapiDataProtector دات نت استفاده میکنند. پس از رمزنگاری، استریم نهایی با فرمت base64 برای درج در HTTP Response آماده خواهد شد.
سؤال: چرا کوکیهای یک کاربر معین لاگین شدهی توسط ASP.NET Identity، در مرورگرهای مختلف متفاوت است؟
هرچند اطلاعاتی مانند ApplicationUser.Id و SecurityStamp برای یک کاربر، در مرورگرهای مختلف یکسان هستند، اما در مرحلهی چهارم، ذکر شد که AuthenticationTicket دارای اطلاعات بیشتری مانند زمان تولید کوکی نیز هست. بنابراین اطلاعات نهایی رمزنگاری شدهی در این حالت که در زمانهای مختلفی تولید شدهاند، یکسان نخواهند بود.
سؤال: در ساب دومینهای مختلف دومین مشخصی، چندین برنامهی مختلف نصب شدهاند. چگونه میتوان از یک سیستم لاگین ASP.NET Identity برای تمام آنها استفاده کرد؟
برای این منظور نیاز هست خاصیت CookieDomain را به صورت صریح مقدار دهی کرد. برای اینکار فایل Startup.Auth.cs را گشوده و CookieAuthenticationOptions را تنظیم کنید:
var cookieAuthenticationOptions = new CookieAuthenticationOptions { AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie, LoginPath = new PathString("/Account/Login"), CookieDomain = ".mydomain.com" };
برای اینکار به کنسول IIS مراجعه کرده و گزینهی machine key آنرا بیابید. در این قسمت بر روی generate keys کلیک کرده و اطلاعات تولیدی را باید به تمام web.configهای موجود کپی کنید:
<machineKey validationKey="DAD9E2B0F9..." decryptionKey="ADD1C39C02..." validation="SHA1" decryption="AES" />
سؤال: برنامههای مختلفی بر روی یک دومین نصب هستند، اما قصد نداریم از سیستم اعتبارسنجی یکپارچهای برای تمام آنها استفاده کنیم. اما اگر در یکی لاگین کنیم، بلافاصله لاگین در برنامهی دوم منقضی میشود، چرا؟
شبیه به همین مساله با Forms Authentication هم وجود دارد. برای رفع آن باید نام کوکیهای هر برنامه را منحصربفرد کنید و از نام پیش فرض کوکیها استفاده نکنید تا بر روی یکدیگر بازنویسی نشوند. برای اینکار خاصیت CookieName شیء CookieAuthenticationOptions را جداگانه مقدار دهی کنید:
CookieName = "my-very-own-cookie-name"
سؤال: لاگین انجام شدهی در برنامهای که از ASP.NET Identity استفاده میکند، زود منقضی میشود؛ چرا؟
برای تنظیم صریح زمان انقضای کوکی ASP.NET Identity نیاز است خاصیت ExpireTimeSpan آنرا مقدار دهی کنید:
app.UseCookieAuthentication(new CookieAuthenticationOptions { ExpireTimeSpan = TimeSpan.FromHours(24.0), });
سؤال: کاربر سیستم ASP.NET Identity از سیستم خارج شدهاست (log off کرده) ولی هنوز میتوان از کوکی پیشین او برای اعتبارسنجی مجدد استفاده کرد. چطور میتوان این نقیصهی امنیتی را برطرف کرد؟
مشکل از اینجا است:
public ActionResult LogOff() { AuthenticationManager.SignOut(); return RedirectToAction("Index", "Home"); }
[HttpPost] [ValidateAntiForgeryToken] public async Task<ActionResult> LogOff() { var user = await UserManager.FindByNameAsync(User.Identity.Name); AuthenticationManager.SignOut(DefaultAuthenticationTypes.ApplicationCookie); await UserManager.UpdateSecurityStampAsync(user.Id); return RedirectToAction("Login", "Account"); }
همچنین بهتر است مقدار validateInterval مربوط به SecurityStampValidator.OnValidateIdentity که به صورت پیش فرض 30 دقیقه است را به مقدار کمتری مانند 5 دقیقه تغییر دهید (تنظیمات OnValidateIdentity مربوط به CookieAuthenticationOptions فایل آغارین برنامه). کار این تنظیم، بررسی اعتبار کوکی، در بازههای زمانی مشخص شدهاست.