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

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

یک نکته‌ی دیگر را که باید متذکر شوم این است که همه اصطلاحاتی که در این مقاله استفاده می‌شوند بر اساس اندروید استادیو و مستندات رسمی گوگل است است؛ به عنوان نمونه عبارت‌های ماژول و پروژه آن چیزی هستند که ما در اندروید استادیو به آن‌ها اشاره می‌کنیم، نه آنچه که کاربران Eclipse به آن اشاره می‌کنند.

یک. برای هر تکه کد و یا متدی که می‌نویسید مستندات کافی قرار دهید و اگر این متد نیاز به مجوز خاصی دارد مانند نمونه زیر، آن را حتما ذکر کنید:
/**
     *
     * <p>
     *   check network is available or not <br/>
     *   internet connection is not matter,for check internet connection refer to IsInternetConnected() Method in this class
     * </p>
       * <p>
     *   Required Permission : <b>android.permission.ACCESS_NETWORK_STATE</b>
     * </p>
     * @param context
     * @return returns true if a network is available
     */
    public boolean isNetworkAvailable(Context context) {

        ConnectivityManager connectivityManager
                = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
        NetworkInfo activeNetworkInfo = connectivityManager.getActiveNetworkInfo();
        return activeNetworkInfo != null && activeNetworkInfo.isConnected();
    }
همچنین اگر، مورد خاص دیگری مثل بالا بود، حتما آن را ذکر کنید. می‌توانید از تگ گذاری در کامنت ها نیز استفاده کنید. از ویژگی‌های کامنت todo در اندروید استادیو این است که میتوانید در حین کار با سیستم گیت نیز از آن بهره ببرید  و قبل از کامیت کردن کد، کدهای todo به شما یادآوری شوند و هر پیکربندی را که لازم دارید، روی آن انجام دهید.



دو. از یک کلاس واحد جهت استفاده از اطلاعات عمومی و یا ثابت‌ها استفاده نمایید. این اطلاعات می‌توانند شامل: مسیرها، آدرس‌های وب سرویس، شماره اختصاصی هر نوتیفیکیشن و .... باشند. برای اینکار میتوان هر کدام از اطلاعات را داخل یک کلاس قرار داد و همه این کلاس‌ها را به صورت استاتیک تعریف کنید تا بدین شکل در دسترس قرار بگیرند (از الگوی singleton هم می‌توان استفاده کرد).
public class ProjectSettings
{
       public static NotificationsId=new NotificationsId();
       public static UrlAddresss=new UrlAddresss();
       public static SdPath=new SdPath();
       ......
}
نحوه صدا زدن هم به همین شکل می‌شود:
ProjectSettings.NotificationsId.UpdateNotificationId
بدین شکل هم به طور ساده و مفهومی صدا زده می‌شود و هم اینکه در همه جای برنامه این ثابت‌ها و مقادیر قابل استفاده هستند. به عنوان مثال به شماره هر نوتیفیکیشن از همه جا دسترسی دارید و هم اینکه شماره‌ای تکراری اشتباها انتخاب نمی‌شود.

سه. حداکثر استفاده از اینترفیس را به خصوص برای UI انجام بدهید:
به عنوان نمونه، بسیاری نمایش یک toast را به شکل زیر انجام می‌دهند:
Toast.makeText(context, message, Toast.LENGTH_SHORT).show();
یا اینکه برای یک دیالوگ مستقیما و در جا همانجا به کدنویسی مشغول می‌شوند. این روش‌ها هیچ مشکلی ندارند ولی در آینده نگهداری کد را مشکل می‌کنند. مثلا تصور کنید شما بسیاری از جاهای برنامه، Toast زدید و حالا قصد دارید در نسخه بعدی برنامه، toast‌های دلخواه و یا custom ایی را ایجاد کنید. در این صورت مجبورید کل برنامه را رصد کرده و هر جا toast هست آن را تغییر دهید. در اینجا هم اصول DRY را نادیده گرفته‌اید و هم زحمت شما زیاد شده‌است و حتی ممکن است یک یا چندتایی از قلم بیفتند. برای دیالوگ‌ها هم بدین صورت خواهد بود و خیلی از مسائل دیگر. به همین جهت استفاده از اینترفیس‌ها توصیه می‌شود و فردا نیز اگر باز یک کلاس دیگر را نوشتید، خیلی راحت آن را با کلاس فعلی تعویض می‌کنید.
public interface IMessageUI
{
    void ShowToast(Context context,String message);
}

public class MessageUI impelement IMessageUI
{
      public void ShowToast(Context context,string message)
       {
              Toast.makeText(context, message, Toast.LENGTH_SHORT).show();
       }
}

چهار
. اگر برای اولین بار است وارد اندروید می‌شوید، خوب چرخه‌های یک شیء، چون اکتیویتی یا فراگمنت را یاد بگیرید تا در آینده با مشکلات خاصی روبرو نشوید.
به عنوان مثال درست است که اولین رویداد فراخوانی در onCreate رخ میدهد ولی همیشه محل مناسبی برای دریافت دیتاها در زمان اولیه نیست. به عنوان مثال تصور کنید که لیستی در اکتیویتی A دارید و به اکتیویتی B می‌روید و یک آیتم به اطلاعات اضافه می‌شود و موقعی که به اکتیویتی A بر می‌گردید، زیاد تعجب نکنید که لیست دقیقا به همان شکل قبلی است و خبری از آیتم جدید نیست.
  چون اکتیویتی در حالت stop بوده و بعد از آن به حالت Resume رفته و تا موقعی که این اکتیویتی از حافظه خارج نشود یا گوشی چرخش نداشته باشد، واکشی دیتاها صورت نخواهد گرفت. پس بهترین مکان در این حالت، رویداد OnStart است که در هر دو وضعیت صدا زده می‌شود؛ یا اینکه در OnRestatr روی آداپتور تغییرات جدید را اعمال کنید تا نیازی به واکشی مجدد داده‌ها نباشد. 



به طور خلاصه نحوه اجرای رویدادها بدین شکل است که ابتدای رویداد OnCreate اجرا می‌شود که هنوز هیچ UI ئی در آن پیاده سازی نشده‌است و شما در اینجا موظفید Layout خود را معرفی کنید. رویداد OnStart بعد از آن موقعی که UI آماده شده است، اجرا می‌گردد. سپس رویداد OnResume اجرا می‌شود.

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

از رویداد Onresume میتوانید برای کارهایی که بین زمان آغاز اکتیویتی و برگشت اکتیویتی مشترکند استفاده کرد. اگر به هر نحوی اکتیویتی به طور کامل پنهان شود٬، به این معناست که شما به اکتیویتی دیگری رفته‌اید رویداد OnStop اجرا شده‌است و در صورت بازگشت، رویداد OnRestart اجرا خواهد شد. ولی اگر مدت طولانی از رویداد OnStop بگذرد احتمال اینکه سیستم مدیریت منابع اندروید، اکتیویتی شما را از حافظه خارج کند زیاد است و رویداد OnDestroy صورت خواهد گرفت. در این حالت دفعه بعد، مجددا همه عملیات از ابتدا آغاز می‌گردند.

پنج. سرویس را با تردهای UI ترکیب نکنید. بعضا دیده می‌شود که کاربران AsyncTask را داخل سرویس استفاده می‌کنند ولی این را بدانید که سرویس یک ترد پردازشی جداگانه است و تضمینی برای ارتباط با UI به شما نمی‌دهند. هر چند گوگل جدیدا تمهیداتی را برای آن اندیشیده است که به شما اجازه اینکار را نمی‌دهد. ولی اگر باز هم اندروید استادیو به شما خورده‌ای نگرفت، خودتان این قانون را اجرا کنید. قرار نیست یک AsyncTask با سرویس ترکیب شود.

شش. اگر برنامه شما قرار است در چندین حالت مختلفی که اتفاق می‌افتد، یک کار خاصی را انجام دهد، برای برنامه‌تان یک Receiver بنویسید و در آن کدهای تکراری را نوشته و در محل‌های مختلف وقوع آن رویدادها، رسیور را صدا بزنید. برای نمونه برنامه تلگرام یک سرویس پیام رسان پشت صحنه دارد که در دو رویداد قرار است اجرا شوند. یکی موقعی که گوشی بوت خود را تکمیل کرده است و در حال آغاز فرایندهای سیستم عامل است و دیگر زمانی است که برنامه اجرا می‌شود. در اینجا تلگرام از یک رسیور سیستمی برای آگاهی از بوت شدن و یک رسیور داخل برنامه جهت آگاهی از اجرای برنامه استفاده میکند و هر دو به یک کلاس از جنس BroadcastReceiver متصلند:
      <receiver android:name=".AppStartReceiver" android:enabled="true">
            <intent-filter>
                <action android:name="org.telegram.start" />
                <action android:name="android.intent.action.BOOT_COMPLETED" />
            </intent-filter>
        </receiver>

public class AppStartReceiver extends BroadcastReceiver {
    public void onReceive(Context context, Intent intent) {
        AndroidUtilities.runOnUIThread(new Runnable() {
            @Override
            public void run() {
                ApplicationLoader.startPushService();
            }
        });
    }
}
برای نام رسیورهای داخلی هم میتوانید مورد شماره 2 را اجرا کنید.
برنامه تلگرام حتی برای حالت‌های پخش هم رسیورها استفاده کرده است که در همین رسیور وضعیت تغییر پلیر مشخص می‌شود:
    <receiver android:name=".MusicPlayerReceiver" >
            <intent-filter>
                <action android:name="org.telegram.android.musicplayer.close" />
                <action android:name="org.telegram.android.musicplayer.pause" />
                <action android:name="org.telegram.android.musicplayer.next" />
                <action android:name="org.telegram.android.musicplayer.play" />
                <action android:name="org.telegram.android.musicplayer.previous" />
                <action android:name="android.intent.action.MEDIA_BUTTON" />
                <action android:name="android.media.AUDIO_BECOMING_NOISY" />
            </intent-filter>
        </receiver>
اینگونه تلگرام میتواند از همه جا سرویس را کنترل کند. مثلا موقعی که دانلود یک موزیک تمام شده، سریعا پخش آن موزیک دانلود شده را آغاز کند.

هفت. اگر از یک ORM برای لایه داده‌ها استفاده می‌کنید (قبلا در سایت جاری در مورد ORM‌های اندروید صحبت کرده‌ایم و  ORM‌های خوش دستی که خودم از آن‌ها استفاده میکنم ActiveAndroid و CPORM هستند که هم کار کردن با آنها راحت است و هم اینکه امکانات خوبی را عرضه می‌کنند) در این نوع ORM‌ها شما نباید انتظار چیزی مانند EF را داشته باشید و در بعضی موارد باید کمی خودتان کمک کنید. به عنوان مثال در Active Android برای ایجاد یک inner join باید به شکل زیر بنویسید:
     From query= new Select()
               .from(Poem.class)
               .innerJoin(BankPoemsGroups.class)
               .on("poems.id=bank_poems_groups.poem")
               .where("BankGroup=?", String.valueOf(groupId));
        return query.execute();
همانطور که می‌بینید بخش‌هایی از آن مثل جوین‌ها و شرط‌ها را باید خودتان تکمیل کنید. از آنجا که ممکن است در آینده نام فیلد تغییر کند یا اینکه در حین انبوهی از کدها، عبارت رشته‌ای را اشتباه وارد کنید، بهتر است به این فرم کار کنید:
@Table(name="poems")
public class Poem extends Model {

    public static String tableName="poems";
    public static  String codeColumn="code";
    public static  String titleColumn="title";
    public static  String bookColumn="book";
    ......

    @Column(name="code",index = true)
    public int Code;

    @Column(name="title")
    public String Title;

    @Column(name="book")
    public Book Book;
.....}
در مدل بالا، نام فیلدها و جداول به صورت استاتیک تعریف شده‌اند. حالا می‌توانیم از این اسامی به راحتی در لایه سرویس استفاده کنیم:
    From query= new Select()
               .from(Poem.class)
               .innerJoin(BankPoemsGroups.class)
               .on(Poem.TableName+"."+ Poem.IdColumn+"="+ BankPoemsGroups.TableName+"."+ BankPoemsGroups.PoemColumn)
               .where(Poem.BankGroupColumn+"=?", String.valueOf(groupId));
        return query.execute();
حالا کمی بهتر شد. هم برای تغییر آینده بهتر شد و هم اینکه احتمال خطای تایپی کاهش یافت. ولی باز هم ایجاد کوئری هنوز سخت است و نوشتن مرتب یک رابطه جوین و شرطی و چسباندن مداوم رشته‌ها کار خسته کننده‌ای است و احتمال خطای سهوی و انسانی هم در آن بالاست. برای رفع این مشکل بهتر است یک کلاس جدید برای ساخت این کوئری‌ها داشته باشیم که یک نمونه از آن را در این پایین می‌بینید:
public class QueryConcater {
  public String GetInnerJoinQuery(String table1,String field1,String table2,String field2)
    {
        String query=table1 +"." +field1+"="+table2+"."+field2;
        return query;
    }
......
}
در ادامه برای مرتب سازی و شرط و ... هم می‌نویسیم:
   return new Select()
                .from(Color.class)
                .innerJoin(ProductItem.class)
                .on(queryConcater.GetInnerJoinQuery(ProductItem.TableName,
                        ProductItem.ColorColumn, Color.TableName))
                .where(queryConcater.WhereConditionQuery
                        (ProductItem.TableName, ProductItem.ProductColumn), productId)
                .execute();
در دستورات بالا از این کلاس دو متد برای کوئری جوین و یکی هم برای ساخت شرط ایجاد شده است و مقادیر به صورت پارامتر داده شده‌اند. این الگو کمک میکند که اگر هم این تکه کد اشتباه باشد، با تغییر یکجا بقیه کدها هم تغییر میکنند و اگر در آینده هم ORM تغییر یافت، نحوه کوئری نویسی‌ها در این کلاس تغییر کنند، نه اینکه در طول لایه سرویس پراکنده باشند.

هشت. سعی کنید همیشه از یک سیستم گزارش خطا در اپلیکیشن خود استفاده کنید. در حال حاضر معروفترین سیستم گزارش خطا Acra است که می‌توانید backend آن را هم از اینجا تهیه کنید و اگر هم نخواستید، سایت Tracepot امکانات خوبی را به رایگان برای شما فراهم می‌کند. از این پس با سیستم آکرا شما به یک سیستم گزارش خطا متصلید که خطاهای برنامه شما در گوشی کاربر به شما گزارش داده خواهد شد. این گزارش‌ها شامل:
  • وضعیت گوشی در حین باز شدن برنامه و در حین خطا چگونه بوده است.
  • مشخصات گوشی
  • این خطا به چه تعداد رخ داده است و برای چه تعداد کاربر
  • گزارش گیری بر اساس اولین تاریخ رخداد خطا و آخرین تاریخ، نسخه سیستم عامل اندروید، ورژن برنامه شما و...
و امکانات دیگر.

نه. آکرا همانند Elmah نمی‌تواند خطاهای catch شده را دریافت کند. برای حل این مشکل عبارت زیر را در catch‌ها بنویسید:
ACRA.getErrorReporter().handleException(caughtException);

ده. بر خلاف سیستم دات نت که شما اجباری به استفاده از Try Catch‌ها ندارید. در جاوا اینگونه نیست و هر متدی که Throw روی آن انجام شده باشد مستلزم  استفاده از catch است. به همین دلیل در شماره نه گفتیم که چگونه باید این مشکل را حل کنیم. ولی در بسیاری از اوقات پیش می‌آید که ما داریم از ماژول‌های متفاوتی استفاده میکنیم که جدا از ماژول اصلی برنامه هستند و این مورد باعث می‌شود که بعضی افراد یا Acra را در همه ماژول‌ها صدا بزنند یا اینکه بی خیال آن شوند. ولی کار راحت‌تر این است که شما هم همانند برنامه نویسان جاوا متد خود را به Throw مزین کنید تا در هنگام استفاده از آن در برنامه اصلی نیاز به catch شدن باشد. در واقع شما نباید catch‌ها را داخل یک کتابخانه جدا و مستقل قرار دهید و روش صحیح هم همین است حالا چه استفاده از آکرا نیاز باشد و چه نباشد.

نمونه اشتباه:
 public  void CopyFile(String source,String destination,CopyFileListener copyFileListener) {
        try {
        InputStream in = new FileInputStream(source);
        OutputStream out = new FileOutputStream(destination);

        long fileLength=new File(source).length();
        // Transfer bytes from in to out
        byte[] buf = new byte[64*1024];
        int len;
        long total=0;
        while ((len = in.read(buf)) > 0) {
            out.write(buf, 0, len);
            total+=len;
            copyFileListener.PublishProgress(fileLength,total);
        }
        in.close();

            out.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
نمونه صحیح:
    public  void CopyFile(String source,String destination,CopyFileListener copyFileListener) throws IOException {

        InputStream in = new FileInputStream(source);
        OutputStream out = new FileOutputStream(destination);

        long fileLength=new File(source).length();
        // Transfer bytes from in to out
        byte[] buf = new byte[64*1024];
        int len;
        long total=0;
        while ((len = in.read(buf)) > 0) {
            out.write(buf, 0, len);
            total+=len;
            copyFileListener.PublishProgress(fileLength,total);
        }
        in.close();

            out.close();
    }
  ادامه دارد...
نظرات اشتراک‌ها
قلم (فونت) فارسی صمیم
به چه طریقی میتونیم اعداد این فونت رو فارسی کنیم؟ من استفاده کردم، اما اعداد رو انگلیسی نشون میده.
نظرات مطالب
نگارش نهایی MBUnit 3 ارائه شد
سلام باز خسته نباشی، مرجعی برای کسی که میخواد تازه شروع کنه حالا چه فارسی یا انگلیسی میشه معرفی کنید.
نظرات مطالب
BloggerToCHM
سلام،
فعلا زود هست. چون باید تاریخ غیر فارسی رو هم اضافه کنم و یک قالب انگلیسی هم براش درست کنم که برای بقیه دنیا هم قابل استفاده باشه.
مطالب
عبارت using و نحوه استفاده صحیح از آن
مثال ساده زیر را که در مورد تعریف یک کلاس Disposable و سپس استفاده از آن توسط عبارت using است را به همراه سه استثنایی که در این متدها تعریف شده است، در نظر بگیرید:
using System;

namespace TestUsing
{
    public class MyResource : IDisposable
    {
        public void DoWork()
        {
            throw new ArgumentException("A");
        }

        public void Dispose()
        {
            throw new ArgumentException("B");
        }
    }

    public static class TestClass
    {
        public static void Test()
        {
            using (MyResource r = new MyResource())
            {
                throw new ArgumentException("C");
                r.DoWork();
            }
        }
    }
}
به نظر شما قطعه کد زیر چه عبارتی را نمایش می‌دهد؟ C یا B یا A؟
try
{
     TestClass.Test();
}
catch (Exception ex)
{
    Console.WriteLine(ex.Message);
}

پاسخ: برخلاف تصور (که احتمالا C است؛ چون قبل از فراخوانی متد DoWork سبب بروز استثناء شده است)، فقط B را در خروجی مشاهده خواهیم کرد!
و این دقیقا مشکلی است که در حین کار با کتابخانه iTextSharp برای اولین بار با آن مواجه شدم. روش استفاده متداول از iTextSharp به نحو زیر است:
using (var pdfDoc = new Document(PageSize.A4))  
{  
   //todo: ...
}
در این بین هر استثنایی رخ دهد، در لاگ‌های خطای سیستم شما تنها خطاهای مرتبط با خود iTextSharp را مشاهده خواهید کرد و نه مشکل اصلی را که در کدهای ما وجود داشته است. البته این یک مشکل عمومی است و اگر «using statement and suppressed exceptions» را در گوگل جستجو کنید به نتایج مشابه زیادی خواهید رسید.
و خلاصه نتایج هم این است:
اگر به ثبت جزئیات خطاهای سیستم اهمیت می‌دهید (یکی از مهم‌ترین مزیت‌های دات نت نسبت به بسیاری از فریم ورک‌های مشابه که حداکثر خطای 0xABC12EF را نمایش می‌دهند)، از using استفاده نکنید! using در پشت صحنه به try/finally ترجمه می‌شود و بهتر است این مورد را دستی نوشت تا اینکه کامپایلر اینکار را به صورت خودکار انجام دهد.
در اینجا باز هم به یک سری کد تکراری try/finally خواهیم رسید و همانطور که در مباحث کاربردهای Action و Func در این سایت ذکر شد، می‌توان آن‌را تبدیل به کدهایی با قابلیت استفاده مجدد کرد. یک نمونه از پیاده سازی آن‌را در این سایت «C# Using Blocks can Swallow Exceptions » می‌توانید مشاهده کنید که خلاصه آن کلاس زیر است:
using System;

namespace Guard
{
    public static class SafeUsing
    {
        public static void SafeUsingBlock<TDisposable>(this TDisposable disposable, Action<TDisposable> action)
            where TDisposable : IDisposable
        {
            disposable.SafeUsingBlock(action, d => d);
        }

        internal static void SafeUsingBlock<TDisposable, T>(this TDisposable disposable, Action<T> action, Func<TDisposable, T> unwrapper)
            where TDisposable : IDisposable
        {
            try
            {
                action(unwrapper(disposable));
            }
            catch (Exception actionException)
            {
                try
                {
                    disposable.Dispose();
                }
                catch (Exception disposeException)
                {
                    throw new AggregateException(actionException, disposeException);
                }

                throw;
            }

            disposable.Dispose();
        }
    }
}
برای استفاده از کلاس فوق مثلا در حالت بکارگیری iTextSharp خواهیم داشت:
new Document(PageSize.A4).SafeUsingBlock(pdfDoc =>
{
  //todo: ...
});
علاوه بر اینکه SafeUsingBlock یک سری از اعمال تکراری را کپسوله می‌کند، از AggregateException نیز استفاده کرده است (معرفی شده در دات نت 4). به این صورت چندین استثنای رخ داده نیز در سطحی بالاتر قابل دریافت و بررسی خواهند بود و استثنایی در این بین از دست نخواهد رفت.
بازخوردهای پروژه‌ها
مشکل در نمایش حروف انگلیسی HtmlHeader
سلام
در HtmlHeader   زمانیکه RunDirection = PdfRunDirection.RightToLeft تنظیم شده کارکترهای انگلیسی نمایش داده نمی‌شوند.
با تشکر
نظرات مطالب
اعتبارسنجی مبتنی بر کوکی‌ها در ASP.NET Core 2.0 بدون استفاده از سیستم Identity
ممنون. با بررسی‌های که کردم متوجه شدم مشکل از این خط کد هست، ولی نتونستم رفعش کنم. آیا جایی را از قلم انداختم برای پیاده سازی؟ با تشکر
  await HttpContext.SignInAsync(
                CookieAuthenticationDefaults.AuthenticationScheme,
                 cookieClaims,
                new AuthenticationProperties
                {
                    IsPersistent =false, // "Remember Me"
                    IssuedUtc = DateTimeOffset.UtcNow,
                    ExpiresUtc = DateTimeOffset.UtcNow.AddDays(loginCookieExpirationDays)
                });

InvalidOperationException: No IAuthenticationSignInHandler is configured to handle sign in for the scheme: Cookies

مطالب
اصلاح Urlها در فایل‌های PDF با استفاده از iTextSharp
نحوه ایجاد لینک در فایل‌های PDF به کمک iTextSharp

حداقل دو نوع لینک را در فایل‌های PDF می‌توان ایجاد کرد:
الف) لینک به منابع خارجی؛ مانند یک وب سایت
ب) لینک به صفحه‌ای داخل فایل PDF
در ادامه مثالی را مشاهده خواهید نمود که شامل هر دو نوع لینک است:
        void WriteFile()
        {
            using (var doc = new Document(PageSize.LETTER))
            {
                using (var fs = new FileStream("test.pdf", FileMode.Create))
                {
                    using (var writer = PdfWriter.GetInstance(doc, fs))
                    {
                        doc.Open();
                        var blueFont = FontFactory.GetFont("Arial", 12, Font.NORMAL, BaseColor.BLUE);
                        doc.Add(new Chunk("Go to URL", blueFont).SetAction(new PdfAction("http://www.google.com/", false)));

                        doc.NewPage();
                        doc.Add(new Chunk("Go to Test", blueFont).SetLocalGoto("entry1"));

                        doc.NewPage();
                        doc.Add(new Chunk("Test").SetLocalDestination("entry1"));

                        doc.Close();
                    }
                }
            }
        }
حاصل این مثال، یک فایل PDF است با سه صفحه. در صفحه اول لینکی به سایت Google وجود دارد. در صفحه دوم، لینکی به صفحه سوم تهیه شده است.
در صفحه سوم یک Local Destination تعبیه شده است. در صفحه دوم به کمک یک Local Goto، لینکی به این مقصد داخلی ایجاد خواهد شد.


اصلاح لینک‌ها در فایل‌های PDF

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




در تصویر اول نحوه ذخیره شدن named destinationها را در یک فایل PDF مشاهده می‌کنید.
در تصویر دوم، ساختار دو نوع لینک تعریف شده در صفحات، مشخص هستند. یکی بر اساس Uri کار می‌کند و دیگری بر اساس GoTo.
کاری را که در ادامه قصد داریم انجام دهیم، تبدیل حالت Uri به GoTo است. برای مثال، در ادامه می‌خواهیم لینک مثال فوق را ویرایش کرده و آن‌را تبدیل به لینکی نمائیم که به entry1 اشاره می‌کند. کدهای انجام اینکار را در ادامه ملاحظه می‌کنید:
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using iTextSharp.text.pdf;

namespace ReplaceLinks
{
    public class ReplacePdfLinks
    {
        Dictionary<string, PdfObject> _namedDestinations;
        PdfReader _reader;

        public string InputPdf { set; get; }
        public string OutputPdf { set; get; }
        public Func<Uri, string> UriToNamedDestination { set; get; }

        public void Start()
        {
            updatePdfLinks();
            saveChanges();
        }

        private PdfArray getAnnotationsOfCurrentPage(int pageNumber)
        {
            var pageDictionary = _reader.GetPageN(pageNumber);
            var annotations = pageDictionary.GetAsArray(PdfName.ANNOTS);
            return annotations;
        }

        private static bool hasAction(PdfDictionary annotationDictionary)
        {
            return annotationDictionary.Get(PdfName.SUBTYPE).Equals(PdfName.LINK);
        }

        private static bool isUriAction(PdfDictionary annotationAction)
        {
            return annotationAction.Get(PdfName.S).Equals(PdfName.URI);
        }

        private void replaceUriWithLocalDestination(PdfDictionary annotationAction)
        {
            var uri = annotationAction.Get(PdfName.URI) as PdfString;
            if (uri == null)
                return;

            if (string.IsNullOrWhiteSpace(uri.ToString()))
                return;

            var namedDestination = UriToNamedDestination(new Uri(uri.ToString()));
            if (string.IsNullOrWhiteSpace(namedDestination))
                return;

            PdfObject entry;
            if (!_namedDestinations.TryGetValue(namedDestination, out entry))
                return;

            annotationAction.Remove(PdfName.S);
            annotationAction.Remove(PdfName.URI);

            var newLocalDestination = new PdfArray();
            annotationAction.Put(PdfName.S, PdfName.GOTO);
            var xRef = ((PdfArray)entry).First(x => x is PdfIndirectReference);
            newLocalDestination.Add(xRef);
            newLocalDestination.Add(PdfName.FITH);
            annotationAction.Put(PdfName.D, newLocalDestination);
        }

        private void saveChanges()
        {
            using (var fileStream = new FileStream(OutputPdf, FileMode.Create, FileAccess.Write, FileShare.None))
            using (var stamper = new PdfStamper(_reader, fileStream))
            {
                stamper.Close();
            }
        }

        private void updatePdfLinks()
        {
            _reader = new PdfReader(InputPdf);
            _namedDestinations = _reader.GetNamedDestinationFromStrings();

            var pageCount = _reader.NumberOfPages;
            for (var i = 1; i <= pageCount; i++)
            {
                var annotations = getAnnotationsOfCurrentPage(i);
                if (annotations == null || !annotations.Any())
                    continue;

                foreach (var annotation in annotations.ArrayList)
                {
                    var annotationDictionary = (PdfDictionary)PdfReader.GetPdfObject(annotation);

                    if (!hasAction(annotationDictionary))
                        continue;

                    var annotationAction = annotationDictionary.Get(PdfName.A) as PdfDictionary;
                    if (annotationAction == null)
                        continue;

                    if (!isUriAction(annotationAction))
                        continue;

                    replaceUriWithLocalDestination(annotationAction);
                }
            }
        }
    }
}
توضیح این کدها بدون ارجاع به تصاویر ارائه شده میسر نیست. کار از متد updatePdfLinks شروع می‌شود. با استفاده از متد GetNamedDestinationFromStrings به کلیه named destinationهای تعریف شده دسترسی خواهیم داشت (تصویر اول). در ادامه Annotations هر صفحه دریافت می‌شوند. اگر به تصویر دوم دقت کنید، به ازای هر صفحه یک سری Annot وجود دارد. داخل اشیاء Annotations، لینک‌ها قرار می‌گیرند. در ادامه این لینک‌ها استخراج شده و تنها مواردی که دارای Uri هستند بررسی خواهند شد.
کار تغییر ساختار PDF در متد replaceUriWithLocalDestination انجام می‌شود. در اینجا آدرس استخراجی به استفاده کننده ارجاع شده و named destination مناسبی دریافت می‌شود. اگر این «مقصد نام دار» در مجموعه مقاصد نام دار PDF جاری وجود داشت، خواص لینک قبلی مانند Uri آن حذف شده و با GoTo به آدرس این مقصد جدید جایگزین می‌شود.
در آخر، توسط یک PdfStamper، اطلاعات تغییر کرده را در فایلی جدید ثبت خواهیم کرد.

یک نمونه از استفاده از کلاس فوق به شرح زیر است:
            new ReplacePdfLinks
            {
                InputPdf = @"test.pdf",
                OutputPdf = "mod.pdf",
                UriToNamedDestination = uri =>
                {
                    if (uri.Host.ToLowerInvariant().Contains("google.com"))
                    {
                        return "entry1";
                    }

                    return string.Empty;
                }
            }.Start();
در این مثال، اگر لینکی به آدرس Google.com اشاره کند، ویرایش شده و اینبار به مقصدی داخلی به نام entry1 ختم خواهد شد.

چند نکته تکمیلی
- اگر قصد داشته باشیم تا لینکی را ویرایش کرده اما تنها Uri آن‌را تغییر دهیم، تنها کافی است URI آن‌را به نحو زیر در متد replaceUriWithLocalDestination ویرایش کنیم:
annotationAction.Put(PdfName.URI, new PdfString("http://www.bing.com/"));
- اگر بجای یک مقصد نام دار، تنها قرار است لینک موجود، به صفحه‌ای مشخص اشاره کند، تغییرات متد replaceUriWithLocalDestination به نحو زیر خواهد بود:
newLocalDestination.Add((PdfObject)_reader.GetPageOrigRef(pageNum: 2));
RemovePdfLinks.7z
مطالب
متفرقه - طنز

یکی از توانایی‌های سایت‌های جمع کننده آمار بازدیدکنندگان یک سایت، مشخص کردن کلمات و جملاتی است که از طریق موتورهای جستجو به سایت مورد نظر ختم شده‌اند که تعدادی از آن‌ها را در مورد وبلاگ جاری ملاحظه می‌کنید!


شرمنده. این یک قلم موجود نیست!



سؤال خوبیه. من خودم هم موندم که چه باید بکنیم!؟



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



از یاهو بیشتر از این انتظار نمیره!



؟؟!



یاد غول چرا جادو نیفتادید؟! متاسفانه هنوز گوگل به این درجه ارتقاء پیدا نکرده!



هنوز منتشر نشده!