مطالب
مدیریت کوکی ها با jQuery
در گذشته نه چندان دور، کوکی‌ها نقش اصلی را در مدیریت کاربران ، و ذخیره اطلاعات کاربران ایفا می‌کردند. ولی بعد از کشف شدن باگ امنیتی ( که ناشی از اشتباه برنامه نویس بود ) در کوکی ها، برای مدتی کنار گذاشته شدند و اکثر اطلاعات کاربران در session  های سمت سرور ذخیره می‌شد.
ذخیره اطلاعات زیاد و نه چندان مهم کاربران در session  های سمت سرور ، بار زیادی را به سخت افزار تحمیل می‌کرد. بعد از این، برنامه نویسان به سمتی استفاده متعادل از هرکدام این‌ها ( کوکی و سشن) رفتند.
اکثر دوستان با مدیریت سمت سرور کوکی‌ها آشنایی دارند ، بنده قصد دارم در اینجا با استفاده از یک پلاگین جی کوئری مدیریت کوکی‌ها را نمایش دهم.
در این برنامه ما از پلاگی jQuery.cookie استفاده می‌کنیم که شما می‌توانید با مراجعه به صفحه این پلاگین اطلاعات کاملی از این پلاگین به دست بیاورید.
کار با این پلاگین بسیار ساده است.
ابتدا فایل پلاگین را به صفحه خودتون اضافه می‌کنید.
<script src="/path/to/jquery.cookie.js"></script>
حالا خیلی راحت می‌توانید با این دستور یک مقدار را در کوکی قرار دهید.
$.cookie('the_cookie', 'the_value');
و برای گرفتن کوکی نوشته شده هم به این صورت عمل می‌کنید.
$.cookie('the_cookie'); // => "the_value"
همان طور که دیدید کار بسیار ساده ای است. ولی قدرت این پلاگین در option  هایی است که در اختیار ما قرار می‌دهد.
مثلا شما می‌توانید انتخاب کنید این کوکی برای چند روز معتبر باشد ، و یا اطلاعات را به صورت json ذخیره و بازیابی کنید، و حتی option  های دیگری برای بحث امنیت کوکی شما.
برای درک بهتر  از قطعه کدی که کمی پیچیده‌تر از مثال منبع است، استفاده می‌کنیم.

به کد زیر توجه کنید :
JavaScript :
    <script type="text/javascript">
        $(function () {
            $('#write').click(function () {
                $.cookie('data', '{"iri":"Iran","usa":"United States"}', { expires: 365, json: true });
                alert('Writed');
            });

            $('#show').click(function () {
                var obj = jQuery.parseJSON($.cookie('data'));
                alert(obj.iri);
            });

            $('#remove').click(function () {
                $.removeCookie('data');
            });
        })
    </script>
HTML :
<body>
    <a href="#" id="write">Write</a>
    <br />
    <a href="#" id="show">Show</a>
    <br />
    <a href="#" id="remove">Remove</a>
</body>
در اینجا ما سه لینک داریم که هر کدام برای ما عملی را نمایش میدهند.
توضیحات کد :
            $('#write').click(function () {
                $.cookie('data', '{"iri":"Iran","usa":"United States"}', { expires: 365, json: true });
                alert('Writed');
            });

با کلیک بر روی لینک Write کوکی data با مقدار مشخص پر می‌شود.
دقت داشته باشید که این مقدار از نوع json انتخاب شده است و در انتها نیز این را مشخص کرده ایم ، همچنین اعلام کرده ایم که این کوکی برای 365 روز معتبر است.
حالا مرورگر خودتان را ببندید و دوباره باز کنید.
این بار بر روی Show کلیک می‌کنیم :
            $('#show').click(function () {
                var obj = jQuery.parseJSON($.cookie('data'));
                alert(obj.iri);
با کلیک بر روی لینک Show  مقدار از کوکی خوانده می‌شود و نمایش داده می‌شود. دقت کنید ، به دلیل اینکه مقدار ذخیره شده ما از نوع json  است باید دوباره این مقدار را pars کنیم تا به مقادیر property آن دسترسی داشته باشیم.
همچنین شما می‌توانید خیلی راحت کوکی ساخته شده را از بین ببرید :
            $('#remove').click(function () {
                $.removeCookie('data');
            });
و یا این که کوکی را برابر null قرار دهید.
نکته ای که باید رعایت کنید و در این مثال هم نیامده است ، این است که ، هنگامی که شما می‌خواهید object ی که با کد تولید کرده اید در کوکی قرار بدهید ، باید از متد JSON.stringify  استفاده کنید و مقدار را به این صورت در کوکی قرار دهید.
$.cookie('data', JSON.stringify(jsonobject), { expires: 365, json: true });
که در اینجا jsonobject ، ابجکتی است که شما تولید کرده اید و قصد ذخیره آن را دارید.
من از این امکان در نسخه بعدی این پروژه استفاده کرده ام ، و به کمک این پلاگین ساده اما مفید ، وب سایت هایی که کاربر نتایج آن را مشاهده کرده است در کوکی کاربر ذخیره می‌کنم تا در مراجعه بعدی میزان تغییرات رنکینگ‌های وب سایت ای در خواست شده را ، به کاربر نمایش دهم. نسخه بعد all-ranks.com تا آخر هفته آینده در سرور اختصاصی ( و نه این هاست رایگان (!)) قرار می‌گیرد و به مرور قسمت هایی که در این پروژه پیاده سازی شده (پلاگین‌های جی کوئری و کد‌های سرور ) در اینجا شرح می‌دهم.
امیدوارم تونسته باشم مطلب مفید و مناسبی  به شما دوستان عزیزم انتقال بدم. 
نظرات مطالب
معماری لایه بندی نرم افزار #4

چون اینجا بحث طراحی مطرح شده یک اصل رو در برنامه‌های وب باید رعایت کرد:

هیچ وقت متن خطای حاصل رو به کاربر نمایش ندید (از لحاظ امنیتی). فقط به ذکر عبارت خطایی رخ داده بسنده کنید. خطا رو مثلا توسط ELMAH لاگ کنید برای بررسی بعدی برنامه نویس.

مطالب
Functional Programming - قسمت چهارم - برخورد با Exception ها
چنانچه قسمت‌های قبلی سری آموزش برنامه نویسی تابعی Functional Programming را مطالعه نکرده‌اید، پیشنهاد میکنم قبلا آن‌ها را  (+  و  +  و  +) قبل از شروع بخوانید. در این قسمت قرار است تاثیر استثناءها (exception) را بر روی کدها بررسی کرده و راهکاری را از جنس functional برایش ارائه کنیم. 



Exception و خوانایی کد

تکه کد زیر را در نظر بگیرید: یک Action معمولی در Asp.Net MVC که یک نام را دریافت کرده و یک کارمندرا ایجاد میکند:

public ActionResult CreateEmployee(string name) { 
    try { 
        ValidateName(name);
        // ادامه کد‌ها return View("با موفقیت ثبت شد");
        }
    catch (ValidationException ex) 
    { 
        return View("خطا", ex.Message);
    }
}

private void ValidateName(string name) { 
    if (string.IsNullOrWhiteSpace(name)) 
        throw new ValidationException("نام نمی‌تواند خالی باشد");

    if (name.Length > 100) 
        throw new ValidationException("نام نمی‌تواند طولانی باشد");
}

در این قطعه کد، در متد ValidateName، در صورت معتبر نبودن ورودی، یک Exception رخ میدهد و بلاک کد try/catch، این exception را دریافت کرده و خطای مناسبی را به کاربر نشان خواهد داد. تا اینجا ظاهرا همه چیز مرتب است و مشکلی ندارد! احتمالا کد‌های مشابه به این کد را زیاد دیده‌اید. در اینجا متد ValidateName، صادق نیست. در قسمت اول، در مورد Honesty صحبت کردیم. به عبارت ساده‌تر شما از امضای این متد نمی‌توانید به نوع خروجی و کاری که قرار است انجام دهد، پی ببرید. در واقع شما همیشه باید پیاده سازی متد را گوشه‌ای، در ذهن خود داشته باشید و برای اطمینان از کاری که متد انجام میدهد، همیشه باید به بدنه‌ی متد برگردیم. اگر به‌خاطر داشته باشید، توابع برنامه نویسی را به توابع ریاضی تشبیه کردیم. پس میتوانیم بگوییم: 

به عبارت دیگر وقتی از exception‌ها برای کنترل flow برنامه استفاده میکنید، مشابه کاری را انجام می‌دهید که دستور GOTO انجام می‌داد. این دستور در روش‌های قبل از برنامه نویسی ساخت یافته وجود داشت و توسط یک دانشمند هلندی به نام آقای دیکسترا حذف شد. وقتی از دستور GOTO یا JUMP استفاده میکنیم، فهمیدن flow برنامه پیچیدگی‌های زیادی را خواهد داشت. چراکه فراخوانی قطعه‌های کد و متد‌ها، وابستگی شدیدی خواهند داشت و البته میتوان گفت استفاده از exception‌ها برای کنترل جریان برنامه، می‌توانند از GOTO هم بد‌تر باشند؛ چرا که exception میتواند از لایه‌های مختلف کد نیز عبور کند.

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

public ActionResult CreateEmployee(string name) { 
    string error = ValidateName(name);

 if (error != string.Empty) 
        return View("خطا", error);
    // ادامه کد‌ها return View("با موفقیت ثبت شد");
}

private string ValidateName(string name) { 

    if (string.IsNullOrWhiteSpace(name)) 
        return "نام نمی‌تواند خالی باشد";

    if (name.Length > 100) 
        return "طول نام نمی‌تواند بیشتر از 100 کاراکتر باشد";

    return string.Empty;
}

با refactor ای که انجام دادیم، متد ValidateName را به یک تابع ریاضی تبدیل کردیم. به این معنا که هر آنچه را که از امضای متد، مشخص است، انجام می‌دهد و در این حالت چیزی مخفی نیست. توجه داشته باشید که این راهکار نهایی ما نیست و لطفا مقاله را تا انتها بخوانید!  



موارد استفاده Exception

با همه‌ی بدی‌هایی که از Exception‌ها گفتیم، با این حساب پس چه زمانی از آن استفاده کنیم؟

  1. Exception‌ها واقعا برای موارد استثنائی هستند.
  2. Exception‌ها برای شرایطی هستند که به معنای واقعی یک باگ باشند.
  3. منتظر رخ دادن Exception نباشیم! 

در توضیح مورد سوم، در اعتبار سنجی داده‌های کاربر (Validation) انتظار داده‌ی نادرستی را می‌توان داشت، پس نمی‌توانیم آن را یک حالت استثنایی بدانیم. معماری زیر را در نظر بگیرید


دیتایی که به API ما ارسال خواهد شد، همیشه شامل عملیات Filter یا به عبارتی Validation است و از آنجایی که می‌توان انتظار استفاده‌ی نادرست یا دیتای نادرست را داشت، نمیتوانیم این را حالتی از استثنائات در نظر بگیریم؛ ولی بر خلاف آن، وقتی در دامین پروژه و ارتباط بین دامین‌های مختلف، دیتایی رد و بدل می‌شود که معتبر نیست، میتوانیم آن را جزء استثناء‌ها در نظر بگیریم. به مثال زیر دقت کنید:

public ActionResult UpdateEmployee(int employeeId, string name) { 
    string error = ValidateName(name);
    
    if (error != string.Empty) 
        return View("Error", error);
    
    Employee employee = GetEmployee(employeeId); 
    employee.UpdateName(name);
}

public class Employee { 

    public void UpdateName(string name){

        if (name == null) 
            throw new ArgumentNullException();
        
        // ادامه کد‌ها }
}

در قطعه کد بالا تصور این است که کلاس Employee و متد UpdateName خارج از دامین می‌باشند. همانطورکه مشاهده میکنید، ما در action controller، از خالی نبودن نام اطمینان حاصل کردیم و سپس آن را به متد UpdateName ارجاع دادیم. ولی اگه به بدنه‌ی متد UpdateName دقت کنید، می‌بینید که مجددا از خالی نبودن نام اطمینان حاصل کرده‌ایم و در صورت خالی بودن، یک Exception را صادر میکنیم! به این مدل چک کردن‌ها در دامین‌های مختلف، معمولا guard clause گفته می‌شود و یک نوع قرارداد بین برنامه نویس هاست. اگر طبق تعریفی که بالاتر ارائه کردیم هم چک کنیم، میتوانیم حدس بزنیم که خالی بودن نام، نشان یک باگ در نرم افزار است! 



مفهوم fail fast

تا اینجا متوجه شدیم که از exception‌ها باید در شرایط استثنائی استفاده کنیم. خوب با توجه به این مساله، چه طور میتوانیم آن‌ها را Handle کنیم؟ این سؤال ما را به مفهومی به نام fail fast می‌رساند. این مفهوم به ما میگوید:

  • کار جاری را به محض یک اتفاق استثنائی باید متوقف کنیم.
  • رعایت این نکته در نهایت ما را به یک نرم افزار پایدار خواهد رساند.


برای درک هر چه بهتر این موضوع، بیایید به عکس این حالت نگاه کنیم؛ اصطلاحا Fail Silently.

متد زیر را ببینید: 

public void ProcessItems(List<Item> items) { 

    foreach (Item item in items) { 
        try { 
            Process(item);
 } 
        catch (Exception ex) 
        { 
            Logger.Log(ex);
 }
 }
 }

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

به صور خلاصه مهم‌ترین مزیت Fail Fast را میتوانیم به صورت زیر خلاصه کنیم:

  • مسیر رسیدن به خطا‌ها سر راست‌تر می‌شود.
  • نرم افزار به پایداری مناسبی خواهد رسید.
  • از اعتبار دیتای ذخیره شده اطمینان خواهیم داشت.


کجا exception‌ها را به دام بیندازیم؟

در یکی از حالت‌های زیر:

  • لاگ کردن
  • متوقف کردن عملیات
  • هیچ گاه در بلاک catch هیچ منطقی را پیاده نکنید.


حالت دیگر در استفاه از کتابخانه‌های دیگران (3rd parties) است. به طور مثال در استفاده از EF ممکن است به دلیل عدم برقراری ارتباط با دیتابیس، خطایی را دریافت کنید. در این حالت با توجه به نکات فوق، با این استثنائات برخورد کنید:

  • جلوی این نوع استثنائات را در پایین‌ترین حد ممکن در کد خود بگیرید.
  • Exception هایی را catch کنید که میدانید در حالت استثناء، چه کاری را می‌توانید انجام دهید.


این به این معنی میباشد که به صورت کلی همه نوع Exception ای را به صورت کلی نگیرید و نوع Exception اختصاصی را در بلاک catch قرار دهید. الان که قرار شد در بعضی از حالت‌ها جلوی استثنائات را بگیریم، خوب است ببینیم چطور باید اینکار را انجام بدیم.

قطعه کد زیر را در نظر بگیرید:

public void CreateCustomer(string name) { 
    Customer customer = new Customer(name); 
    bool result = SaveCustomer(customer);
    if (!result) { 
        MessageBox.Show("Error connecting to the database. Please try again later.");
    }
}

private bool SaveCustomer(Customer customer) { 
    try { 
        using (MyContext context = new MyContext()) { 
            context.Customers.Add(customer);
         context.SaveChanges();
        } 
        return true;
    }
    catch (DbUpdateException ex) { 
        if (ex.Message == "Unable to open the DB connection") 
            return false; 
        else 
            throw;
    }
}

همانطور که مشاهده میکنید، در حالتیکه خطایی از نوع DbUpdateException رخ میدهد، مقدار بازگشتی متد را برابر با false میکنیم. اما مشکلی که وجود دارد این است که این‌کار به اندازه‌ی کافی خوانا نیست. همچنین honest بودن متد را نقض کرده‌ایم. به علاوه مشکل بزرگتر دیگر این است که ما با بازگرداندن یک مقدار bool، میتوانیم به متد بالاتر اطلاع بدهیم که کار مورد نظر انجام شده یا نه، اما در مورد دلیل انجام نشدن آن، هیچ کاری نمیتوانیم بکنیم. پیشنهاد من برای مقدار بازگشتی متد‌هایی که احتمال انجام نشدن کاری در آن‌ها می‌رود، استفاده از یک نوع اختصاصی می‌باشد.

در اینجا من این نوع را با نام کلاس Result معرفی میکنم. انتظاری که از این نوع اختصاصی داریم:

  • Honest بودن متد را نگه دارد.
  • خروجی متد را به همراه وضعیت اجرا شدن برگرداند.
  • شکل یکسانی را برای خطا‌ها داشته باشد.
  • فقط جلوی خطا‌های غیر منتظره را بگیرد.


برای مثال کد بالا را به شکل زیر refactor می‌کنیم:

private Result SaveCustomer(Customer customer) { 
    try { 

        using (var context = new MyContext()) { 

            context.Customers.Add(customer); 
            context.SaveChanges();
 } 

        return Result.Ok();
    } 
    catch (DbUpdateException ex) { 
        if (ex.Message == "Unable to open the DB connection") 
            Result.Fail(ErrorType.DatabaseIsOffline);

        if (ex.Message.Contains("IX_Customer_Name")) 
            return Result.Fail(ErrorType.CustomerAlreadyExists);

        throw;
    }
}

به عبارتی با این روش میتوانیم از انجام شدن/نشدن عملیات اطمینان حاصل کنیم و خروجی/دلیل انجام نشدن را نیز میتوانیم برگردانیم.

اگر به امضای متد‌های زیر نگاه کنیم، می‌توانیم آن‌ها را طبق الگوی CQS دسته‌بندی کنیم: 

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

مسیرراه‌ها
ASP.NET MVC
              مطالب
              مستند سازی ASP.NET Core 2x API توسط OpenAPI Swagger - قسمت سوم - تکمیل مستندات یک API با کامنت‌ها
              در قسمت قبل موفق شدیم بر اساس OpenAPI specification endpoint تنظیم شده، رابط کاربری خودکاری را توسط ابزار Swagger-UI تولید کنیم. در ادامه می‌خواهیم این مستندات تولید شده را غنی‌تر کرده و کیفیت آن‌را بهبود دهیم.


              استفاده از XML Comments برای بهبود کیفیت مستندات API

              نوشتن توضیحات XML ای برای متدها و پارامترها در پروژه‌های دات‌نتی، روشی استاندارد و شناخته شده‌است. برای نمونه در AuthorsController، می‌خواهیم توضیحاتی را به اکشن متد GetAuthor آن اضافه کنیم:
              /// <summary>
              /// Get an author by his/her id
              /// </summary>
              /// <param name="authorId">The id of the author you want to get</param>
              /// <returns>An ActionResult of type Author</returns>
              [HttpGet("{authorId}")]
              public async Task<ActionResult<Author>> GetAuthor(Guid authorId)
              در این حالت اگر برنامه را اجرا کنیم، این توضیحات XMLای هیچ تاثیری را بر روی OpenAPI specification تولیدی و در نهایت Swagger-UI تولید شده‌ی بر اساس آن، نخواهد داشت. برای رفع این مشکل، باید به فایل OpenAPISwaggerDoc.Web.csproj مراجعه نمود و تولید فایل XML متناظر با این توضیحات را فعال کرد:
              <Project Sdk="Microsoft.NET.Sdk.Web">
                <PropertyGroup>
                  <TargetFramework>netcoreapp2.2</TargetFramework>
                  <AspNetCoreHostingModel>InProcess</AspNetCoreHostingModel>
                  <GenerateDocumentationFile>true</GenerateDocumentationFile>
                </PropertyGroup>
              پس از تنظیم خاصیت GenerateDocumentationFile به true، با هر بار Build برنامه، فایل xml ای مطابق نام اسمبلی برنامه، در پوشه‌ی bin آن تشکیل خواهد شد؛ مانند فایل bin\Debug\netcoreapp2.2\OpenAPISwaggerDoc.Web.xml در این مثال.
              اکنون نیاز است وجود این فایل را به تنظیمات SwaggerDoc در کلاس Startup برنامه، اعلام کنیم:
              namespace OpenAPISwaggerDoc.Web
              {
                  public class Startup
                  {
                      public void ConfigureServices(IServiceCollection services)
                      {
                          services.AddSwaggerGen(setupAction =>
                          {
                              setupAction.SwaggerDoc(
                                  // ... 
                                 );
              
                              var xmlCommentsFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
                              var xmlCommentsFullPath = Path.Combine(AppContext.BaseDirectory, xmlCommentsFile);
                              setupAction.IncludeXmlComments(xmlCommentsFullPath);
                          });
                      }
              در متد IncludeXmlComments، بجای ذکر صریح نام و مسیر فایل OpenAPISwaggerDoc.Web.xml، بر اساس نام اسمبلی جاری، نام فایل XML مستندات تعیین و مقدار دهی شده‌است.
              پس از این تنظیمات اگر برنامه را اجرا کنیم، در Swagger-UI حاصل، این تغییرات قابل مشاهده هستند:




              افزودن توضیحات به Response

              تا اینجا توضیحات پارامترها و متدها را افزودیم؛ اما response از نوع 200 آن هنوز فاقد توضیحات است:


              علت را نیز در تصویر فوق مشاهده می‌کنید. قسمت responses در OpenAPI specification، اطلاعات خودش را از اسکیمای مدل‌های مرتبط دریافت می‌کند. بنابراین نیاز است کلاس DTO متناظر با Author را به نحو ذیل تکمیل کنیم:
              using System;
              
              namespace OpenAPISwaggerDoc.Models
              {
                  /// <summary>
                  /// An author with Id, FirstName and LastName fields
                  /// </summary>
                  public class Author
                  {
                      /// <summary>
                      /// The id of the author
                      /// </summary>
                      public Guid Id { get; set; }
              
                      /// <summary>
                      /// The first name of the author
                      /// </summary>
                      public string FirstName { get; set; }
              
                      /// <summary>
                      /// The last name of the author
                      /// </summary>
                      public string LastName { get; set; }
                  }
              }
              مشکل! در این حالت اگر برنامه را اجرا کنیم، خروجی این توضیحات را در قسمت schemas مشاهده نخواهیم کرد. علت اینجا است که چون اسمبلی OpenAPISwaggerDoc.Models با اسمبلی OpenAPISwaggerDoc.Web یکی نیست و آن‌را از پروژه‌ی اصلی خارج کرده‌ایم، به همین جهت نیاز است ابتدا به فایل OpenAPISwaggerDoc.Models.csproj مراجعه و GenerateDocumentationFile آن‌را فعال کرد:
              <Project Sdk="Microsoft.NET.Sdk">
                <PropertyGroup>
                  <TargetFramework>netstandard2.0</TargetFramework>
                  <GenerateDocumentationFile>true</GenerateDocumentationFile>
                </PropertyGroup>
              </Project>
              سپس باید فایل xml مستندات آن‌را به صورت مجزایی به تنظیمات ابتدایی برنامه معرفی نمود:
              namespace OpenAPISwaggerDoc.Web
              {
                  public class Startup
                  {
                      public void ConfigureServices(IServiceCollection services)
                      {
                          services.AddSwaggerGen(setupAction =>
                          {
                              setupAction.SwaggerDoc(
                // ...
                                 );
                              var xmlFiles = Directory.GetFiles(AppContext.BaseDirectory, "*.xml", SearchOption.TopDirectoryOnly).ToList();
                              xmlFiles.ForEach(xmlFile => setupAction.IncludeXmlComments(xmlFile));
                          });
                      }
              با توجه به اینکه تمام فایل‌های xml تولید شده در آخر به پوشه‌ی bin\Debug\netcoreapp2.2 کپی می‌شوند، فقط کافی است حلقه‌ای را تشکیل داده و تمام آن‌ها را یکی یکی توسط متد IncludeXmlComments به تنظیمات AddSwaggerGen اضافه کرد.

              در این حالت اگر مجددا برنامه را اجرا کنیم، خروجی ذیل را در قسمت schemas مشاهده خواهیم کرد:



              بهبود مستندات به کمک Data Annotations

              اگر به اکشن متد UpdateAuthor در کنترلر نویسندگان دقت کنیم، چنین امضایی را دارد:
              [HttpPut("{authorId}")]
              public async Task<ActionResult<Author>> UpdateAuthor(Guid authorId, AuthorForUpdate authorForUpdate)
              جائیکه موجودیت Author را در پروژه‌ی OpenAPISwaggerDoc.Entities تعریف کرده‌ایم، نام و نام خانوادگی اجباری بوده و دارای حداکثر طول 150 حرف، هستند. قصد داریم همین ویژگی‌ها را به DTO دریافتی این متد، یعنی AuthorForUpdate نیز اعمال کنیم:
              using System.ComponentModel.DataAnnotations;
              
              namespace OpenAPISwaggerDoc.Models
              {
                  /// <summary>
                  /// An author for update with FirstName and LastName fields
                  /// </summary>
                  public class AuthorForUpdate
                  {
                      /// <summary>
                      /// The first name of the author
                      /// </summary>
                      [Required]
                      [MaxLength(150)]
                      public string FirstName { get; set; }
              
                      /// <summary>
                      /// The last name of the author
                      /// </summary>
                      [Required]
                      [MaxLength(150)]
                      public string LastName { get; set; }
                  }
              }
              پس از افزودن ویژگی‌های Required و MaxLength به این DTO، خروجی Sawgger-UI به صورت زیر بهبود پیدا می‌کند:



              بهبود مستندات متد HttpPatch با ارائه‌ی یک مثال

              دو نگارش از اکشن متد UpdateAuthor در این مثال موجود هستند:
              یکی HttpPut است
              [HttpPut("{authorId}")]
              public async Task<ActionResult<Author>> UpdateAuthor(Guid authorId, AuthorForUpdate authorForUpdate)
              و دیگری HttpPatch:
              [HttpPatch("{authorId}")]
              public async Task<ActionResult<Author>> UpdateAuthor(
                          Guid authorId,
                          JsonPatchDocument<AuthorForUpdate> patchDocument)
              این مورد آرام آرام در حال تبدیل شدن به یک استاندارد است؛ چون امکان Partial updates را فراهم می‌کند. به همین جهت نسبت به HttpPut، کارآیی بهتری را ارائه می‌دهد. اما چون پارامتر دریافتی آن از نوع ویژه‌ی JsonPatchDocument است و مثال پیش‌فرض مستندات آن، آنچنان مفهوم نیست:


              بهتر است در این حالت مثالی را به استفاده کنندگان از آن ارائه دهیم تا در حین کار با آن، به مشکل برنخورند:
              /// <summary>
              /// Partially update an author
              /// </summary>
              /// <param name="authorId">The id of the author you want to get</param>
              /// <param name="patchDocument">The set of operations to apply to the author</param>
              /// <returns>An ActionResult of type Author</returns>
              /// <remarks>
              /// Sample request (this request updates the author's first name) \
              /// PATCH /authors/id \
              /// [ \
              ///     { \
              ///       "op": "replace", \
              ///       "path": "/firstname", \
              ///       "value": "new first name" \
              ///       } \
              /// ] \
              /// </remarks>
              [HttpPatch("{authorId}")]
              public async Task<ActionResult<Author>> UpdateAuthor(
                  Guid authorId,
                  JsonPatchDocument<AuthorForUpdate> patchDocument)
              در اینجا در حین کامنت نویسی، می‌توان از المان remarks، برای نوشتن توضیحات اضافی مانند ارائه‌ی یک مثال، استفاده کرد که در آن op و path معادل‌های بهتری را نسبت به مستندات پیش‌فرض آن پیدا کرده‌‌اند. در اینجا برای ذکر خطوط جدید باید از \ استفاده کرد؛ وگرنه خروجی نهایی، در یک سطر نمایش داده می‌شود:



              روش کنترل warningهای کامنت‌های تکمیل نشده

              با فعالسازی GenerateDocumentationFile در فایل csproj برنامه، کامپایلر، بلافاصله برای تمام متدها و خواص عمومی که دارای کامنت نیستند، یک warning را صادر می‌کند. یک روش برطرف کردن این مشکل، افزودن کامنت به تمام قسمت‌های برنامه است. روش دیگر آن، تکمیل خواص کامپایلر، جهت مواجه شدن با عدم وجود کامنت‌ها در فایل csproj برنامه است:
              <Project Sdk="Microsoft.NET.Sdk.Web">
                <PropertyGroup>
                  <TargetFramework>netcoreapp2.2</TargetFramework>
                  <AspNetCoreHostingModel>InProcess</AspNetCoreHostingModel>
              
                  <GenerateDocumentationFile>true</GenerateDocumentationFile>
                  <TreatWarningsAsErrors>false</TreatWarningsAsErrors>
                  <WarningsAsErrors>NU1605;</WarningsAsErrors>
                  <NoWarn>1701;1702;1591</NoWarn>
                </PropertyGroup>
              توضیحات:
              - اگر می‌خواهید خودتان را مجبور به کامنت نویسی کنید، می‌توانید نبود کامنت‌ها را تبدیل به error کنید. برای این منظور خاصیت TreatWarningsAsErrors را به true تنظیم کنید. در این حالت هر کامنت نوشته نشده، به صورت یک error توسط کامپایلر گوشزد شده و برنامه کامپایل نخواهد شد.
              - اگر TreatWarningsAsErrors را خاموش کردید، هنوز هم می‌توانید یکسری از warningهای انتخابی را تبدیل به error کنید. برای مثال NU1605 ذکر شده‌ی در خاصیت WarningsAsErrors، مربوط به package downgrade detection warning است.
              - اگر به warning نبود کامنت‌ها دقت کنیم به صورت عبارات warning CS1591: Missing XML comment for publicly visible type or member شروع می‌شود. یعنی  CS1591 مربوط به کامنت‌های نوشته نشده‌است. می‌توان برای صرفنظر کردن از آن، شماره‌ی این خطا را بدون CS، توسط خاصیت NoWarn ذکر کرد.


              کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید: OpenAPISwaggerDoc-03.zip

              در قسمت بعد، مشکل خروجی تولید response از نوع 200 را که در قسمت دوم به آن اشاره کردیم، بررسی خواهیم کرد.
              مطالب
              ASP.NET MVC #17

              فیلترهای امنیتی ASP.NET MVC

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


              بررسی اعتبار درخواست (Request Validation) در ASP.NET MVC

              ASP.NET MVC امکان ارسال اطلاعاتی را که دارای تگ‌های HTML باشند، نمی‌دهد. این قابلیت به صورت پیش فرض فعال است و جلوی ارسال انواع و اقسام اطلاعاتی که ممکن است سبب بروز حملات XSS Cross site scripting attacks شود را می‌گیرد. نمونه‌ای از خطای نمایش داده:

              A potentially dangerous Request.Form value was detected from the client (Html="<a>"). 

              بنابراین تصمیم گرفته شده صحیح است؛ اما ممکن است در قسمتی از سایت نیاز باشد تا کاربران از یک ویرایشگر متنی پیشرفته استفاده کنند. خروجی این نوع ویرایشگرها هم HTML است. در این حالت می‌توان صرفا برای متدی خاص امکانات Request Validation را به کمک ویژگی ValidateInput غیرفعال کرد:

              [HttpPost]
              [ValidateInput(false)]
              public ActionResult CreateBlogPost(BlogPost post)

              از ASP.NET MVC 3.0 به بعد راه حل بهتری به کمک ویژگی AllowHtml معرفی شده است. غیرفعال کردن ValidateInput ‌ایی که معرفی شد، بر روی تمام خواص شیء BlogPost اعمال می‌شود. اما اگر فقط بخواهیم که مثلا خاصیت Text آن از مکانیزم بررسی اعتبار درخواست خارج شود، بهتر است دیگر از ویژگی ValidateInput استفاده نشده و به نحو زیر عمل گردد:

              using System;
              using System.Web.Mvc;

              namespace MvcApplication14.Models
              {
              public class BlogPost
              {
              public int Id { set; get; }
              public DateTime AddDate { set; get; }
              public string Title { set; get; }

              [AllowHtml]
              public string Text { set; get; }
              }
              }

              در اینجا فقط خاصیت Text مجاز به دریافت محتوای HTML ایی خواهد بود. اما خاصیت Title چنین مجوزی را ندارد. همچنین دیگر نیازی به استفاده از ویژگی ValidateInput غیرفعال شده نیز نخواهد بود.
              به علاوه همانطور که در قسمت‌های قبل نیز ذکر شد، خروجی Razor به صورت پیش فرض Html encoded است مگر اینکه صریحا آن‌را تبدیل به HTML کنیم (مثلا استفاده از متد Html.Raw). به عبارتی خروجی Razor در حالت پیش فرض در مقابل حملات XSS مقاوم است مگر اینکه آگاهانه بخواهیم آن‌را غیرفعال کنیم.

              مطلب تکمیلی
              مقابله با XSS ؛ یکبار برای همیشه!



              فیلتر RequireHttps

              به کمک ویژگی یا فیلتر RequireHttps، تمام درخواست‌های رسیده به یک متد خاص باید از طریق HTTPS انجام شوند و حتی اگر شخصی سعی به استفاده از پروتکل HTTP معمولی کند، به صورت خودکار به HTTPS هدایت خواهد شد:

              [RequireHttps]
              public ActionResult LogOn()
              {
              }


              فیلتر ValidateAntiForgeryToken

              نوع دیگری از حملات که باید در برنامه‌های وب به آن‌ها دقت داشت به نام CSRF یا Cross site request forgery معروف هستند.
              برای مثال فرض کنید کاربری قبل از اینکه بتواند در سایت شما کار خاصی را انجام دهد، نیاز به اعتبار سنجی داشته باشد. پس از لاگین شخص و ایجاد کوکی و سشن معتبر، همین شخص به سایت دیگری مراجعه می‌کند که در آن مهاجمی بر اساس وضعیت جاری اعتبار سنجی او مثلا لینک حذف کاربری یا افزودن اطلاعات جدیدی را به برنامه ارائه می‌دهد. چون سشن شخص و کوکی مرتبط به سایت اول هنوز معتبر هستند و شخص سایت را نبسته است، «احتمال» اجرا شدن درخواست مهاجم بالا است (خصوصا اگر از مرورگرهای قدیمی استفاده کند).
              بنابراین نیاز است بررسی شود آیا درخواست رسیده واقعا از طریق فرم‌های برنامه ما صادر شده است یا اینکه شخصی از طریق سایت دیگری اقدام به جعل درخواست‌ها کرده است.
              برای مقابله با این نوع خطاها ابتدا باید داخل فرم‌های برنامه از متد Html.AntiForgeryToken استفاده کرد. کار این متد ایجاد یک فیلد مخفی با مقداری منحصربفرد بر اساس اطلاعات سشن جاری کاربر است، به علاوه ارسال یک کوکی خودکار تا بتوان از تطابق اطلاعات اطمینان حاصل کرد:

              @using (Html.BeginForm()) {
              @Html.AntiForgeryToken()

              در مرحله بعد باید فیلتر ValidateAntiForgeryToken جهت بررسی مقدار token دریافتی به متد ثبت اطلاعات اضافه شود:

              [HttpPost]
              [ValidateAntiForgeryToken]
              public ActionResult CreateBlogPost(BlogPost post)

              در اینجا مقدار دریافتی از فیلد مخفی فرم :

              <input name="__RequestVerificationToken" type="hidden" value="C0iPfy/3T....=" />

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

              [ValidateAntiForgeryToken(Salt="1234")]

              @Html.AntiForgeryToken(salt:"1234")

              به این ترتیب tokenهای تولید شده در فرم‌های مختلف سایت یکسان نخواهند بود.
              به علاوه باید دقت داشت که ValidateAntiForgeryToken فقط با فعال بودن کوکی‌ها در مرورگر کاربر کار می‌کند و اگر کاربری پذیرش کوکی‌ها را غیرفعال کرده باشد، قادر به ارسال اطلاعاتی به برنامه نخواهد بود. همچنین این فیلتر تنها در حالت HttpPost قابل استفاده است. این مورد هم در قسمت‌های قبل تاکید گردید که برای مثال بهتر است بجای داشتن لینک delete در برنامه که با HttpGet ساده کار می‌کند،‌ آن‌را تبدیل به HttpPost نمود تا میزان امنیت برنامه بهبود یابد. از HttpGet فقط برای گزارشگیری و خواندن اطلاعات از برنامه استفاده کنید و نه ثبت اطلاعات.
              بنابراین استفاده از AntiForgeryToken را به چک لیست اجباری تولید تمام فرم‌های برنامه اضافه نمائید.

              مطلب مشابه
              Anti CSRF module for ASP.NET



              فیلتر سفارشی بررسی Referrer

              یکی دیگر از روش‌های مقابله با CSRF، بررسی اطلاعات هدر درخواست ارسالی است. اگر اطلاعات Referrer آن با دومین جاری تطابق نداشت، به معنای مشکل دار بودن درخواست رسیده است. فیلتر سفارشی زیر می‌تواند نمونه‌ای باشد جهت نمایش نحوه بررسی UrlReferrer درخواست رسیده:

              using System.Web.Mvc;

              namespace MvcApplication14.CustomFilter
              {
              public class CheckReferrerAttribute : AuthorizeAttribute
              {
              public override void OnAuthorization(AuthorizationContext filterContext)
              {
              if (filterContext.HttpContext != null)
              {
              if (filterContext.HttpContext.Request.UrlReferrer == null)
              throw new System.Web.HttpException("Invalid submission");

              if (filterContext.HttpContext.Request.UrlReferrer.Host != "mysite.com")
              throw new System.Web.HttpException("This form wasn't submitted from this site!");
              }

              base.OnAuthorization(filterContext);
              }
              }
              }

              و برای استفاده از آن:
              [HttpPost]
              [CheckReferrer]
              [ValidateAntiForgeryToken]
              public ActionResult DeleteTask(int id)


              نکته‌ای امنیتی در مورد آپلود فایل‌ها در ASP.NET

              هر جایی که کاربر بتواند فایلی را به سرور شما آپلود کند، مشکلات امنیتی هم از همانجا شروع خواهند شد. مثلا در متد Upload قسمت 11 این سری، منعی در آپلود انواع فایل‌ها نیست و کاربر می‌تواند انواع و اقسام شل‌ها را جهت تحت کنترل گرفتن سایت و سرور آپلود و اجرا کند. راه حل چیست؟
              از همان روش امنیتی مورد استفاده توسط تیم ASP.NET MVC استفاده می‌کنیم. فایل web.config قرار گرفته در پوشه Views را باز کنید (نه فایل وب کانفیگ ریشه اصلی سایت‌را). چنین تنظیمی را می‌توان مشاهده کرد:
              برای IIS6 :

              <system.web>
              <httpHandlers>
              <add path="*" verb="*" type="System.Web.HttpNotFoundHandler"/>
              </httpHandlers>
              </system.web>
              برای IIS7 :
              <system.webServer>
              <handlers>
              <remove name="BlockViewHandler"/>
              <add name="BlockViewHandler" path="*" verb="*" preCondition="integratedMode" type="System.Web.HttpNotFoundHandler" />
              </handlers>
              </system.webServer>


              تنظیم فوق، موتور اجرایی ASP.NET را در این پوشه خاص از کار می‌اندازد. به عبارتی اگر شخصی مسیر یک فایل aspx یا cshtml یا هر فایل قرار گرفته در پوشه Views را مستقیما در مرورگر خود وارد کند، با پیغام HttpNotFound مواجه خواهد شد.
              این روش هم با ASP.NET Web forms سازگار است و هم با ASP.NET MVC؛ چون مرتبط است به موتور اجرایی ASP.NET که هر دوی این فریم ورک‌ها برفراز آن معنا پیدا می‌کنند.
              بنابراین در پوشه فایل‌های آپلودی به سرور خود یک web.config را با محتوای فوق ایجاد کنید (و فقط باید مواظب باشید که این فایل حین آپلود فایل‌های جدید، overwrite نشود. مهم!). به این ترتیب این مسیر دیگر از طریق مرورگر قابل دسترسی نخواهد بود (با هر محتوایی). سپس برای ارائه فایل‌های آپلودی به کاربران از روش زیر استفاده کنید:

              public ActionResult Download()
              {
              return File(Server.MapPath("~/Myfiles/test.txt"), "text/plain");
              }

              مزیت مهم روش ذکر شده این است که کاربران مجاز به آپلود هر نوع فایلی خواهند بود و نیازی نیست لیست سیاه تهیه کنید که مثلا فایل‌هایی با پسوند‌های خاص آپلود نشوند (که در این بین ممکن است لیست سیاه شما کامل نباشد ...).




              علاوه بر تمام فیلترهای امنیتی که تاکنون بررسی شدند،‌ فیلتر دیگری نیز به نام Authorize وجود دارد که در قسمت‌های بعدی بررسی خواهد شد.
              نظرات اشتراک‌ها
              مقایسه‌ای بین امکانات Rider و Visual Studio
              من هم مدت‌ها با VS کار کردم و اخیرا دارم با rider کار میکنم. بنظرم مواردی که گفتین کاملا محسوس و درسته. من با نسخه پیش نمایش vs2022 هم کار کرده بودم اما برای کسایی که ممکنه یه جاهایی حس کنن چرا مایکروسافت فلان امکان رو برای راحتی یا سرعت توسعه برنامه نویس نذاشته به شدت rider رو پیشنهاد میکنم. اگر بخوام جور دیگه بگم میشه گفت کسایی که تا حالا تو vs از ریشارپر استفاده میکردن اگر میخوان لذت بیشتری از این محصول ببرن بهتره rider رو هم یه تستی بکنن 

              مزایا vs نسبت به rider:
              + بروز بودن با آخرین نسخه و تکنولوژی‌های Microsoft
              + امکان استفاده از IntelliCode completions که با هوش مصنوعی پیشنهاد‌های جالبی رو میده!

              مزایای rider نسبت به vs:
              + امکانات مختلف جهت تنظیم IDE و کد نویسی سریع تر
              + IDE روان‌تر و سریع‌تر به کمک ایندکس گذاری‌های رایدر

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

              برای تهیه فایل PDF، به غیر از کتابخانه‌های گزارش گیری تجاری، می‌توان از کتابخانه‌ی iTextSharp که گزینه‌ای سورس باز، با کیفیت و محبوب است، استفاده کرد. متاسفانه این کتابخانه دارای محیط گرافیکی طراحی گزارش نیست و کار با آن فقط از طریق کدنویسی میسر است که صد البته انعطاف پذیری و پویایی قابل توجهی را برای تهیه‌ی گزارش نسبت به ابزار‌های طراحی گرافیکی، در اختیار برنامه نویس قرار می‌دهد. البته می‌توان از برنامه‌ی Open Office برای طراحی قالب گزارش نیز استفاده کرد، اما من پس از استفاده، به کیفیت و انعطاف پذیری و امکانات مورد نظرم نتوانستم دست یابم و تصمیم گرفتم برای تهیه‌ی کارت، مستقیما با iTextSharp کد نویسی انجام دهم.

              در این مقاله به نحوه‌ی تهیه یک کارت به فرمت PDF با استفاده از کتابخانه iTextSharp خواهیم پرداخت که این کتابخانه به فناوری خاصی گره نخورده است و در تمامی برنامه‌های ASP.NET ، WPF، Windows Form و در کل هر کجا که دات نت فریمورک در دسترس است، قابل استفاده می‌باشد.

              فرض کنید کارتی به شکل زیر می‌خواهیم بسازیم (تمامی تصاویر از سطح اینترنت جمع آوری شده‌اند):


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

               فرقی نمی‌کند که تکنولوژی مورد استفاده شما چیست، برای سادگی کار این مثال را با یک Console Application آغاز کنید. برای نصب iTextSharp نیز فرمان زیر را در کنسول NuGet وارد کنید:
              Install-Package iTextSharp


              شروع کار با iTextSharp

              معمولا برای کار با iText یک سری روال تکراری از قبیل انتخاب نام فایل نهایی، تعریف فونت، سایز کاغذ، حاشیه بندی و ... را باید طی کنید که کدهای آن را در ذیل مشاهده می‌کنید:

                          var fileStream = new FileStream("card.pdf", FileMode.Create, FileAccess.Write, FileShare.None);
              
                          var docFont = GetFont();
              
                          var pageSize = PageSize.A6.Rotate(); // سایز کارت را اینجا باید مشخص کرد
              
                          var doc = new Document(pageSize);
              
                          doc.SetMargins(18f, 18f, 15f, 2f);
              
                          var pdfWriter = PdfWriter.GetInstance(doc, fileStream);
              
                          doc.Open();
              -  در اینجا سایز کارت، بر روی کاغذ A6 در حالت افقی قرار داده شده است. بدیهی است که مطابق نیاز خودتان می‌توانید این سایز را تغییر دهید.
              -  تابع GetFont یک تابع کمکی است که در سورس نهایی ارائه شده است و نکته تعریف فونت در iTextSharp  در آن رعایت شده است.
               - بقیه موارد نیز جزء الزامات کار با این کتابخانه است.


              برای درج عکس به صورت شفاف در پس زمینه کارت باید از کد زیر استفاده کرد:

                          //  درج لوگوی مسابقات به صورت شفاف در پس زمینه
                          var canvas = pdfWriter.DirectContentUnder;
                          var logoImg = Image.GetInstance(competitionImagePath);
                          logoImg.SetAbsolutePosition(0, 0);
                          logoImg.ScaleAbsolute(pageSize);
                          var graphicsState = new PdfGState { FillOpacity = 0.2F };
                          canvas.SetGState(graphicsState);
                          canvas.AddImage(logoImg);
               

              چیدمان و طرح بندی بندی عناصر در iTextSharp

              برای طراحی کارت یا کلا کار طراحی، باید با نحوه‌ی قرار دادن و طرح بندی عناصر مثل تصاویر و نوشته‌ها و ابزارهای مورد نیاز برای این کار، آشنا شوید. خوشبختانه در iText برای این کار ابزارهای خوبی وجود دارد.

              حتما با تگ Table در HTML آشنایی دارید. در سال‌های دور، حتی کل صفحه‌ی وب را به وسیله‌ی Table ساختار دهی می‌کردند. در iTextSharp نیز کلاسی به نام PdfPTable در دسترس است که می‌توان از آن به عنوان قالبی برای قرار دادن عناصر، در صفحه استفاده کرد. این Table همانند هر جدولی دارای یک سری سطر و ستون است که می‌توانیم عناصر مورد نظرمان مثل تصویر و نوشته و... را در آن قرار دهیم. 

              همانطور که از تگ Table در HTML می‌توان برای رسم جدول و قرار دادن عناصر در سطر و ستون‌های آن استفاده کرد، در iText نیز می‌توان از کلاس PdfPTable برای ترسیم جدول و از متد AddCell آن برای افزودن سلول به آن استفاده کرد. کار با کلاس PdfPTable نیز ساده است. کافی هست به هنگام ساخت نمونه‌ای از آن، در سازنده‌اش تعداد ستون‌های جدول را ذکر کنید. سپس با استفاده از متد AddCell آن، پارامتری از جنس PdfPCell برای آن ارسال کنید تا به جدول، سلول جدیدی اضافه شود و در صورتیکه تعداد سلول‌های جدید، از تعداد ستون‌های تعریف شده بیشتر شود، iText به صورت خودکار سلول‌های اضافی را به ردیف جدیدی منتقل می‌کند. البته برای افزودن سطر و ستون، روش‌های دیگری نیز هست؛ ولی گویا  روش مرجح همین روش است.  کلاس PdfPCell که نقش سلول‌های جدول را بازی می‌کند، نیز می‌تواند شامل متن، تصویر و یا حتی یک جدول تودرتو باشد.
                
              در ادامه کدهای جدول بالایی کارت شامل لوگوی دانشگاه، عنوان مسابقات و عکس فرد را مشاهده می‌کنید:
                          // جدولی که برای چیدمان عناصر ارم دانشگاه و عنوان و عکس شخص استفاده می‌شود
                          var topTable = new PdfPTable(3)
                          {
                              WidthPercentage = 100,
                              RunDirection = PdfWriter.RUN_DIRECTION_RTL,
                              ExtendLastRow = false,
                          };
              
                          var universityLogoImage = Image.GetInstance(universityLogoPath);
              
                          universityLogoImage.ScaleAbsolute(70, 100);
              
                          topTable.AddCell(new PdfPCell(universityLogoImage)
                          {
                              HorizontalAlignment = Element.ALIGN_LEFT,
                              Border = 0
                          });
              
                          topTable.AddCell(new PdfPCell(new Phrase("کارت مسابقات دانشگاه آزاد اسلامی", docFont))
                          {
                              RunDirection = PdfWriter.RUN_DIRECTION_RTL,
                              HorizontalAlignment = Element.ALIGN_CENTER,
                              Border = 0,
                          });
              
                          var userImage = Image.GetInstance(userModel.ImagePath);
                          userImage.Border = Rectangle.TOP_BORDER | Rectangle.RIGHT_BORDER | Rectangle.BOTTOM_BORDER | Rectangle.LEFT_BORDER;
                          userImage.BorderWidth = 1f;
                          userImage.BorderColor = new BaseColor(204, 204, 204); // gray color
                          userImage.ScaleAbsolute(70, 100);
              
                          topTable.AddCell(new PdfPCell(userImage)
                          {
                              HorizontalAlignment = 2,
                              Border = 0
                          });
              
                          int[] topTableColumnsWidth = { 10, 25, 10 };
              
                          topTable.SetWidths(topTableColumnsWidth);
              
                          doc.Add(topTable);
              -  در ابتدا یک جدول سه ستونه تعریف شده است. تعداد ستون‌ها در هنگام نمونه سازی از کلاس PdfPTable، در سازنده‌ی آن ذکر شده است.
              -  در iText برای کار با تصاویر، باید از کلاس Image و متد GetInstance فراهم شده توسط خود کتابخانه استفاده کرد. سپس این تصویر را باید به عنوان پارامتر به سلول جدول ارسال کرد.
              -  به کمک متد AddCell، می‌توان به جدول، سلول اضافه کرد و به صورت خودکار سلول‌های جدیدی که از تعداد ستون‌ها بیشتر می‌شوند، به سطر جدید منتقل می‌شوند.
              -  اگر می‌خواهید در سلولی متنی نمایش دهید، از کلاس Phrase و تعیین صریح فونت آن استفاده کنید؛ چرا که در غیر این صورت ممکن است متون فارسی نمایش داده نشود.
              -  در انتها هم جدول مورد نظر را باید به شی doc از جنس کلاس Document تعریف شده اضافه کرد.
               
              بدیهی هست که اطلاعات شخص مثل نام، نام خانوادگی و ... را نیز باید در یک جدول چهار ستونه قرار داد و نکته‌ی خاص اضافه‌تری ندارد. 

              حال اگر بیاییم این تکه کدها را کنار هم قرار بدهیم به کدی قابل اجرا خواهیم رسید.
              ابتدا کلاسی را که در برگیرنده‌ی اطلاعات فرد است، تعریف می‌کنیم:
                  public class UserModel
                  {
                      public string FirstName { get; set; }
                      public string LastName { get; set; }
                      public string StudentNumber { get; set; }
                      public string NationalCode { get; set; }
                      public string UniversityName { get; set; }
                      public string ImagePath { get; set; }
                  }
               
              - سپس کلاس CardReport را که اصل و اساس بحث ما بود، تعریف می‌کنیم.
              using System;
              using System.IO;
              using iTextSharp.text;
              using iTextSharp.text.pdf;
              using Font = iTextSharp.text.Font;
              using Image = iTextSharp.text.Image;
              using Rectangle = iTextSharp.text.Rectangle;
              
              namespace ITextSharpCardSample
              {
                  public class CardReport
                  {
                      public static void Generate(UserModel userModel, string competitionImagePath, string universityLogoPath)
                      {
                          var fileStream = new FileStream("card.pdf", FileMode.Create, FileAccess.Write, FileShare.None);
              
                          var docFont = GetFont();
              
                          var pageSize = PageSize.A6.Rotate(); // سایز کارت را اینجا باید مشخص کرد
              
                          var doc = new Document(pageSize);
              
                          doc.SetMargins(18f, 18f, 15f, 2f);
              
                          var pdfWriter = PdfWriter.GetInstance(doc, fileStream);
              
                          doc.Open();
              
                          //  درج لوگوی مسابقات به صورت شفاف در پس زمینه
                          var canvas = pdfWriter.DirectContentUnder;
                          var logoImg = Image.GetInstance(competitionImagePath);
                          logoImg.SetAbsolutePosition(0, 0);
                          logoImg.ScaleAbsolute(pageSize);
                          var graphicsState = new PdfGState { FillOpacity = 0.2F };
                          canvas.SetGState(graphicsState);
                          canvas.AddImage(logoImg);
              
              
                          // جدولی که برای چیدمان عناصر ارم دانشگاه و عنوان و عکس شخص استفاده می‌شود
                          var topTable = new PdfPTable(3)
                          {
                              WidthPercentage = 100,
                              RunDirection = PdfWriter.RUN_DIRECTION_RTL,
                              ExtendLastRow = false,
                          };
              
                          var universityLogoImage = Image.GetInstance(universityLogoPath);
              
                          universityLogoImage.ScaleAbsolute(70, 100);
              
                          topTable.AddCell(new PdfPCell(universityLogoImage)
                          {
                              HorizontalAlignment = Element.ALIGN_LEFT,
                              Border = 0
                          });
              
                          topTable.AddCell(new PdfPCell(new Phrase("کارت مسابقات دانشگاه آزاد اسلامی", docFont))
                          {
                              RunDirection = PdfWriter.RUN_DIRECTION_RTL,
                              HorizontalAlignment = Element.ALIGN_CENTER,
                              Border = 0,
                          });
              
                          var userImage = Image.GetInstance(userModel.ImagePath);
                          userImage.Border = Rectangle.TOP_BORDER | Rectangle.RIGHT_BORDER | Rectangle.BOTTOM_BORDER | Rectangle.LEFT_BORDER;
                          userImage.BorderWidth = 1f;
                          userImage.BorderColor = new BaseColor(204, 204, 204); // gray color
                          userImage.ScaleAbsolute(70, 100);
              
                          topTable.AddCell(new PdfPCell(userImage)
                          {
                              HorizontalAlignment = 2,
                              Border = 0
                          });
              
                          int[] topTableColumnsWidth = { 10, 25, 10 };
              
                          topTable.SetWidths(topTableColumnsWidth);
              
                          doc.Add(topTable);
              
              
                          // جدول مشخصات شرکت کننده مثل نام و نام خانوادگی
                          var infoTable = new PdfPTable(4)
                          {
                              WidthPercentage = 100,
                              RunDirection = PdfWriter.RUN_DIRECTION_RTL,
                              ExtendLastRow = false,
                              SpacingBefore = 15,
                          };
              
                          infoTable.AddCell(new PdfPCell(new Phrase("نام:", docFont))
                          {
                              RunDirection = PdfWriter.RUN_DIRECTION_RTL,
                              HorizontalAlignment = Element.ALIGN_LEFT,
                              Border = 0,
                              PaddingBottom = 15
                          });
              
                          infoTable.AddCell(new PdfPCell(new Phrase(userModel.FirstName, docFont))
                          {
                              RunDirection = PdfWriter.RUN_DIRECTION_RTL,
                              HorizontalAlignment = Element.ALIGN_LEFT,
                              Border = 0
                          });
              
                          infoTable.AddCell(new PdfPCell(new Phrase("نام خانوادگی:", docFont))
                          {
                              RunDirection = PdfWriter.RUN_DIRECTION_RTL,
                              HorizontalAlignment = Element.ALIGN_LEFT,
                              Border = 0
                          });
              
                          infoTable.AddCell(new PdfPCell(new Phrase(userModel.LastName, docFont))
                          {
                              RunDirection = PdfWriter.RUN_DIRECTION_RTL,
                              HorizontalAlignment = Element.ALIGN_LEFT,
                              Border = 0
                          });
              
                          infoTable.AddCell(new PdfPCell(new Phrase("شماره\nدانشجویی:", docFont))
                          {
                              RunDirection = PdfWriter.RUN_DIRECTION_RTL,
                              HorizontalAlignment = Element.ALIGN_LEFT,
                              Border = 0,
                              PaddingBottom = 15
                          });
              
                          infoTable.AddCell(new PdfPCell(new Phrase(userModel.StudentNumber, docFont))
                          {
                              RunDirection = PdfWriter.RUN_DIRECTION_RTL,
                              HorizontalAlignment = Element.ALIGN_LEFT,
                              Border = 0
                          });
              
                          infoTable.AddCell(new PdfPCell(new Phrase("کد ملی:", docFont))
                          {
                              RunDirection = PdfWriter.RUN_DIRECTION_RTL,
                              HorizontalAlignment = Element.ALIGN_LEFT,
                              Border = 0
                          });
              
                          infoTable.AddCell(new PdfPCell(new Phrase(userModel.NationalCode, docFont))
                          {
                              RunDirection = PdfWriter.RUN_DIRECTION_RTL,
                              HorizontalAlignment = Element.ALIGN_LEFT,
                              Border = 0
                          });
              
                          infoTable.AddCell(new PdfPCell(new Phrase("واحد دانشگاهی:", docFont))
                          {
                              RunDirection = PdfWriter.RUN_DIRECTION_RTL,
                              HorizontalAlignment = Element.ALIGN_LEFT,
                              Border = 0
                          });
              
                          infoTable.AddCell(new PdfPCell(new Phrase(userModel.UniversityName, docFont))
                          {
                              RunDirection = PdfWriter.RUN_DIRECTION_RTL,
                              HorizontalAlignment = Element.ALIGN_LEFT,
                              Border = 0
                          });
              
              
                          // دو سلول بعدی صرفا جهت تکمیل شدن یک ردیف است تا عملکرد صحیح خود را داشته باشد
                          infoTable.AddCell(new PdfPCell(new Phrase("", docFont))
                          {
                              RunDirection = PdfWriter.RUN_DIRECTION_RTL,
                              HorizontalAlignment = Element.ALIGN_LEFT,
                              Border = 0
                          });
              
                          infoTable.AddCell(new PdfPCell(new Phrase("", docFont))
                          {
                              RunDirection = PdfWriter.RUN_DIRECTION_RTL,
                              HorizontalAlignment = Element.ALIGN_LEFT,
                              Border = 0
                          });
              
              
                          int[] infoTableColumnsWidth = { 20, 15, 20, 15 };
              
                          infoTable.SetWidths(infoTableColumnsWidth);
              
                          doc.Add(infoTable);
              
                          doc.Close();
                      }
              
                      private static Font GetFont()
                      {
                          const string fontName = "Iranian Sans";
              
                          if (FontFactory.IsRegistered(fontName))
                              return FontFactory.GetFont(fontName, BaseFont.IDENTITY_H, BaseFont.EMBEDDED);
              
                          var fontPath = "Fonts/irsans.ttf"; // مسیر فونت
              
                          FontFactory.Register(fontPath);
              
                          return FontFactory.GetFont(fontName, BaseFont.IDENTITY_H, BaseFont.EMBEDDED);
                      }
              
                  }
              
              }
              نکته: حتما به تعریف فونت در پوشه‌ی Fonts و عکس‌ها در پوشه Images توجه فرمایید.

              و در انتها نحوه‌ی استفاده از کلاس CardReport در یک برنامه‌ی Console: 
                  class Program
                  {
                      static void Main(string[] args)
                      {
                          var userModel = new UserModel
                          {
                              FirstName = "علی",
                              LastName = "احمدی",
                              NationalCode = "1234567890",
                              StudentNumber = "23242342",
                              UniversityName = "آزاد",
                              ImagePath = "Images/avatar.jpg"
                          };
              
                          CardReport.Generate(userModel, "Images/competition_logo.jpg", "Images/university_logo.png");
              
                          System.Diagnostics.Process.Start("card.pdf");
              
                      }
                  }
                   
              مطالب
              بررسی ORM های مناسب جهت استفاده در اندروید
              با آمدن ORM‌ها به دنیای برنامه نویسی، کار برنامه نویسی نسبت به قبل ساده‌تر و راحت‌تر شد. عدم استفاده کوئری‌های دستی، پشتیبانی از چند دیتابیس و از همه مهمتر و اصلی‌ترین هدف این ابزار "تنها درگیری با اشیا و مدل شیء گرایی" کار را پیش از پیش آسان‌تر نمود.
              در این بین به راحتی می‌توان چندین نمونه از این ORM‌ها را  نام برد مثل IBatis , Hibernate ,Nhibernate و EF که از معروفترین آن‌ها هستند.
              من در حال حاضر قصد شروع یک پروژه اندرویدی را دارم و دوست دارم بجای استفاده‌ی از Sqlitehelper، از یک ORM مناسب بهره ببرم که چند سوال برای من پیش می‌آید. آیا ORM ای برای آن تهیه شده است؟ اگر آری چندتا و کدامیک از آن‌ها بهتر هستند؟ شاید در اولین مورد کتابخانه‌ی Hibernate جاوا را نام ببرید؛ ولی توجه به این نکته ضروری است که ما در مورد پلتفرم موبایل و محدودیت‌های آن صحبت می‌کنیم. یک کتابخانه همانند Hibernate مطمئنا برای یک برنامه اندروید چه از نظر حجم نهایی برنامه و چه از نظر حجم بزرگش در اجرا، مشکل زا خواهد بود و وجود وابستگی‌های متعدد و دارا بودن بسیاری از قابلیت‌هایی که اصلا در بانک‌های اطلاعاتی موبایل قابل اجرا نیست، باعث می‌شود این فریمورک انتخاب خوبی برای یک برنامه اندروید نباشد.

              معیارهای انتخاب یک فریم ورک مناسب برای موبایل:
              • سبک بودن: مهمترین مورد سبک بودن آن است؛ چه از لحاظ اجرای برنامه و چه از لحاظ حجم نهایی برنامه
              • سریع بودن: مطمئنا ORM‌های طراحی شده‌ی موجود، از سرعت خیلی بدی برخوردار نخواهند بود؛ اگر سر زبان هم افتاده باشند. ولی باز هم انتخاب سریع بودن یک ORM، مورد علاقه‌ی بسیاری از ماهاست.
              • یادگیری آسان و کانفیگ راحت تر.

              OrmLight

              این فریمورک مختص اندروید طراحی نشده ولی سبک بودن آن موجب شده‌است که بسیاری از برنامه نویسان از آن در برنامه‌های اندرویدی استفاده کنند. این فریم ورک جهت اتصالات JDBC و Spring و اندروید طراحی شده است.

              نحوه معرفی جداول در این فریمورک به صورت زیر است:
              @DatabaseTable(tableName = "users")
              public class User {
                  @DatabaseField(id = true)
                  private String username;
                  @DatabaseField
                  private String password;
               
                  public User() {
                      // ORMLite needs a no-arg constructor
                  }
                  public User(String username, String password) {
                      this.username = username;
                      this.password = password;
                  }
               
                  // Implementing getter and setter methods
                  public String getUserame() {
                      return this.username;
                  }
                  public void setName(String username) {
                      this.username = username;
                  }
                  public String getPassword() {
                      return this.password;
                  }
                  public void setPassword(String password) {
                      this.password = password;
                  }
              }
              با استفاده از کلمات کلیدی DatabaseTable@ در بالای کلاس و DatabaseField@ در بالای هر پراپرتی به معرفی جدول و فیلدهای جدول می‌پردازیم.
              سورس این فریمورک را می‌توان در گیت هاب یافت و مستندات آن در این آدرس قرار دارند.


              SugarORM
              این فریمورک مختص اندروید طراحی شده است. یادگیری آن بسیار آسان است و به راحتی به یاد می‌ماند. همچنین جداول مورد نیاز را به طور خودکار خواهد ساخت. روابط یک به یک و یک به چند را پشتیبانی می‌کند و عملیات CURD را با سه متد Save,Delete و Find که البته FindById هم جزء آن است، پیاده سازی می‌کند.

              برای استفاده از این فریمورک نیاز است ابتدا متادیتا‌های زیر را به فایل manifest اضافه کنید:
              <meta-data android:name="DATABASE" android:value="my_database.db" />
              <meta-data android:name="VERSION" android:value="1" />
              <meta-data android:name="QUERY_LOG" android:value="true" />
              <meta-data android:name="DOMAIN_PACKAGE_NAME" android:value="com.my-domain" />
              برای تبدیل یک کلاس به جدول هم از کلاس این فریم ورک ارث بری می‌کنیم:
              public class User extends SugarRecord<User> {
                  String username;
                  String password;
                  int age;
                  @Ignore
                  String bio; //this will be ignored by SugarORM
               
                  public User() { }
               
                  public User(String username, String password,int age){
                      this.username = username;
                      this.password = password;
                      this.age = age;
                  }
              }
              بر خلاف OrmLight که باید فیلد جدول را معرفی می‌کردید، اینجا تمام پراپرتی‌ها به اسم فیلد شناخته می‌شوند؛ مگر اینکه در بالای آن از عبارت Ignore@ استفاده کنید.

              باقی عملیات آن از قبیل اضافه کردن یک رکورد جدید یا حذف رکورد(ها) به صورت زیر است:
              User johndoe = new User(getContext(),"john.doe","secret",19);
              johndoe.save(); //ذخیره کاربر جدید در دیتابیس
              
              
              //حذف تمامی کاربرانی که سنشان 19 سال است
              List<User> nineteens = User.find(User.class,"age = ?",new int[]{19});
              foreach(user in nineteens) {
                  user.delete();
              }
              برای اطلاعات بیشتر به مستندات آن رجوع کنید.


              GreenDAO
              موقعیکه بحث کارآیی و سرعت پیش می‌آید نام GreenDAO هست که می‌درخشد. طبق گفته‌ی سایت رسمی آن این فریمورک میتواند در ثانیه چند هزار موجودیت را اضافه و به روزرسانی و بارگیری نماید. این لیست حاوی برنامه‌هایی است که از این فریمورک استفاده می‌کنند. جدول زیر مقایسه‌ای است بین این کتابخانه و OrmLight که نشان میدهد 4.5 برابر سریعتر از OrmLight عمل می‌کند.

              غیر از این‌ها در زمینه‌ی حجم هم حرف‌هایی برای گفتن دارد. حجم این کتابخانه کمتر از 100 کیلوبایت است که در اندازه‌ی APK اثر چندانی نخواهد داشت.

              آموزش راه اندازی  آن در اندروید استادیو، سورس آن در گیت هاب و مستندات رسمی آن.


              Active Android
              این کتابخانه از دو طریق فایل JAR و به شیوه maven قابل استفاده است که می‌توانید روش استفاده‌ی از آن را در این لینک ببینید و سورس اصلی آن هم در این آدرس قرار دارد. بعد از اینکه کتابخانه را به پروژه اضافه کردید، دو متادیتای زیر را که به ترتیب نام دیتابیس و ورژن آن هستند، به manifest اضافه کنید:
              <meta-data android:name="AA_DB_NAME" android:value="my_database.db" />
              <meta-data android:name="AA_DB_VERSION" android:value="1" />
              بعد از آن  عبارت ;()ActiveAndroid.Initialize را در اکتیویتی‌های مدنظر اعمال کنید:
              public class MyActivity extends Activity {
                  @Override
                  public void onCreate(Bundle savedInstanceState) {
                      super.onCreate(savedInstanceState);
                      ActiveAndroid.initialize(this);
               
                      //ادامه برنامه
                  }
              }
              برای معرفی کلاس‌ها به جدول هم از دو اعلان Table و Column مانند کد زیر به ترتیب برای معرفی جدول و فیلد استفاده می‌کنیم.
              @Table(name = "User")
              public class User extends Model {
                  @Column(name = "username")
                  public String username;
               
                  @Column(name = "password")
                  public String password;
               
                  public User() {
                      super();
                  }
               
                  public User(String username,String password) {
                      super();
                      this.username = username;
                      this.password = password;
                  }
              }
              جهت اطلاعات بیشتر در مورد این کتابخانه به مستندات آن رجوع کنید.


              ORMDroid
               از آن دست کتابخانه‌هایی است که سادگی و کم حجم بودن شعار آنان است و سعی دارند تا حد ممکن همه چیز را خودکار کرده و کمترین کانفیگ را نیاز داشته باشد. حجم فعلی آن حدود 20 کیلوبایت بوده و نمی‌خواهند از 30 کیلوبایت تجاوز کند.

              برای استفاده‌ی از آن ابتدا دو خط زیر را جهت معرفی تنظیمات به manifest اضافه کنید:
              <meta-data
                android:name="ormdroid.database.name"
                android:value="your_database_name" />
              
              <meta-data
                android:name="ormdroid.database.visibility"
                android:value="PRIVATE||WORLD_READABLE||WORLD_WRITEABLE" />
              برای آغاز کار این کتابخانه، عبارت زیر را در هرجایی که مایل هستید مانند کلاس ارث بری شده از Application یا در ابتدای هر اکتیویتی که مایل هستید بنویسید. طبق مستندات آن صدا زدن چندباره این متد هیچ اشکالی ندارد.
              ORMDroidApplication.initialize(someContext);
              معرفی مدل جدول بانک اطلاعاتی هم از طریق ارث بری از کلاس Entity می‌باشد .
              public class Person extends Entity {
                public int id;
                public String name;
                public String telephone;
              }
              
              //====================
              
              Person p = Entity.query(Person.class).where("id=1").execute();
              p.telephone = "555-1234";
              p.save();
              
              // یا
              
              Person person = Entity.query(Person.class).where(eql("id", id)).execute();
              p.telephone = "555-1234";
              p.save();
              کد بالا دقیقا یادآوری به EF هست ولی حیف که از Linq پشتیبانی نمی‌شود.
              سورس آن در گیت هاب

              در اینجا سعی کردیم تعدادی از کتابخانه‌های محبوب را معرفی کنیم ولی تعداد آن به همین جا ختم نمی‌شود. ORM‌های دیگری نظیر AndRom و سایر ORM هایی که در این لیست معرفی شده اند وجود دارند.


              نکته نهایی اینکه خوب می‌شود دوستانی که از این ORM‌های مختص اندروید استفاده کرده اند؛ نظراتشان را در مورد آن‌ها بیان کنند و مزایا و معایب آن‌ها را بیان کنند.