بازخوردهای دوره
پیاده سازی دکمه «بیشتر» یا «اسکرول نامحدود» به کمک jQuery در ASP.NET MVC
- شاید خطایی وجود دارد که باید بررسی کنید: نحوه استفاده از افزونه Firebug برای دیباگ برنامه‌های ASP.NET مبتنی بر jQuery 
- در اینجا محدودیتی در مورد تعداد پارامترها ندارید. برای مثال لینک قسمت مطالب سایت جاری به این صورت است:
https://www.dntips.ir/postsarchive#/page/1/date/asc
در اینجا پارامترهای شماره صفحه، فیلد و صعودی و نزولی بودن آن ذکر شدند. پارامترهای دیگری را هم اگر علاقمند بودید (مانند زبان یا هر مورد دیگری) اضافه کنید. فایل jquery.InfiniteScroll.js پیوست شده یک مثال در این مورد است (نحوه تنظیم و دریافت پارامترها و نحوه‌ی واکنش به پارامترهای دریافتی). مستندات رسمی آن‌را هم فراموش نکنید.
نظرات مطالب
تاریخ شمسی برای blogger !
سلام
راه استاندارد آن‌را اینجا توضیح داده‌ام:
https://www.dntips.ir/2009/06/blog-post.html
نیاز به فید نظرات سایت دارد و اگر share نیست با این راه نمی‌شود.
راه دیگر هم اینجا توضیح داده شده است:
http://www.google.com/support/blogger/bin/answer.py?hl=en&answer=42522
مطالب
یکپارچه سازی CKEditor با Lightbox
در یک پروژه من احتیاج داشتم تا عکس هایی که کاربر از طریق ckeditor آپلود می‌کنه ، به صورت خودکار با lightbox  یکپارچه شه و به صورت گالری عکس نمایش داده شود.
همان طور که می‌دونید مکانیزم عملکرد اکثر پلاگین‌های lightbox  به صورت زیر است:
<a href="orginal size of image directory"><img src="thumb(smaller size) image directory" /></a>

خب معلوم میشه که آدرس عکسی که به صورت کوچیک)اصطلاحا بند انگشتی) به نمایش در میاد باید داخل تگ img ، وآدرس عکس با سایز اصلی ، داخل تگ a قرار بگیره.

پس تقریبا معلوم شد که چه باید بکنیم.

ابتدا باید عکسی که آپلود شده را به صورت خودکار به اندازه کوچکتر تغییر ابعاد داده و سپس آدرس عکس تغییر ابعاد داده شده ، را به عنوان آدرس عکس آپلود شده به ckeditor باز گردانیم  که در شکل زیر آن را نشان داده ام:

ولی  ما فقط  آدرس  عکس تغییر ابعاد داده شده متعلق به تگ img  را ، به ckeditor  داده ایم و برای اینکه با lightbox  به خوبی کار کند، احتیاج داریم که آدرس عکس با سایز اصلی را داخل تگ  a  قرار دهیم، که در ckeditor می‌توانیم از قسمت پیوندها این کار را انجام دهیم، و همه‌ی این عملیات باید به صورت خودکار انجام شود.


خب تا اینجا خلاصه ای از آنچه باید انجام شود را گفتم.

در کل منطقی که باید پیاده شود بدین گونه است:

1-  پیاده سازی ckeditor
2-  فعال کردن قسمت آپلود عکس ckeditor
3-  آپلود کردن عکس و تغییر اندازه آن به کمک  کلاس WebImage
4-  برگرداندن آدرس عکس‌های آپلود شده به ckeditor و روش‌های پیاده سازی آن

نکات:
الف) من در اینجا از ASP.NET MVC استفاده می‌کنم .شما  نیز می‌توانید منطق پیاده سازی شده را به راحتی و با کمی تغییرات به پروژه خود اعمال کنید.
ب) کدهایی که من نوشتم 100% بهینه نیستند و کاملا هم بدون اشکال نیستند ولی  نیازهای شما را برآورده می‌کند.
ج)آدرس دادن فایل‌های من کاملا صحیح نیستند و بهتر است شما از T4MVC استفاده کنید.

مرحله اول: دانلود و پیاده سازی ckeditor در صفحه

ابتدا به سایت ckeditor.com مراجعه  و از قسمت دانلود، آن را دانلود کرده و سپس از حالت فشرده درآورده، در فولدری مثلا به نام Scripts در پروژه‌ی خود بریزید.
همان طور که می‌دانید ckeditor با یک textarea  یکپارچه میشود ، که من ساده‌ترین آن را برای شما شرح می‌دهم.
اول از همه شما باید یک فایل جاوا اسکریپت متعلق به ckeditor  را در صفحه ای که از آن می‌خواهید استفاده کنید به صفحه ضمیمه کنید. نام این فایل ckeditor.js هست که در فایل دانلودی در داخل پوشه‌ی ckeditor دیده میشود.
 
<script src="/Scripts/ckeditor/ckeditor.js" type="text/javascript"></script>
 نکته: بهتر است که این ارجاع را قبل از بسته شدن تگ body انجام دهید یعنی درست قبل از </body>.
خب فرض کنید که یک textarea به شکل زیر داریم. برای اینکه این textarea  با ckeditor  یکپارچه شود کافیست که class آن را بر روی ckeditor  قرار دهید.
<textarea id="content" name="content" class="ckeditor" ></textarea>
نکته: حتما برای textarea خود id را نیز تعیین کنید ، چون در غیر این صورت  ckeditor کار نخواهد کرد.

تا به اینجای کار توانستیم ckeditor را اجرا کنیم.
در مرحله بعد سراغ فعال سازی آپلود عکس در آن می‌رویم.

مرحله دوم: فعال سازی آپلود عکس در ckeditor

از شواهد این طور به نظر میاد که به صورت پیشفرض این امکان غیر فعال است و باید به صورت دستی آن را فعال کنیم.
 برای این کار کافیست که از مستندات ckeditor  کمک بگیریم.
خب یکی از راحت‌ترین این روش‌ها این است که قبل از بسته شدن تگ بادی فایل جاوا اسکریپت زیر را بنویسیم:
 
<script type="text/javascript">
        CKEDITOR.replace( 'content' , { filebrowserImageUploadUrl: '/Admin/UploadImage' } );
</script>
نکته اول: این خط کدی که نوشتیم مرحله اول را باطل می‌کنه یعنی احتیاج نبود که مرحله اول را طی کنیم و می‌توانستیم مستقیما از همین مرحله شروع کنیم.
توضیحات:
آرگومان اول CKEDITOR.replace که در اینجا content  است ، در واقع  id همان textarea ای هست که می‌خواهیم ckeditor  روی آن اعمال شود.
آرگومان دوم نام کنترلر و اکشن را برای آپلود فایل مشخص می‌کنه.(از منطق ASP.Net MVC استفاده کردم)
خب تا اینجا اگر تست بگیرید میبینید که قسمت زیر برایتان فعال شده.

مرحله سوم: آپلود کردن عکس و تغییر اندازه آن به کمک  کلاس WebImage

برای این که ببینم که این فرم آپلود ckeditor ، چه پارامترهایی را به اکشن متد من ارسال می‌کنه ، از فایرباگ کمک گرفتم:

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

خب همون طور که یپش از این گفته بودم ما نیاز داریم که عکس آپلود شده را به ابعاد کوچکتر تغییر اندازه داده  و اصطلاحا از آن به عنوان تصویر بند انگشتی استفاده کنیم. آدرس این عکس کوچک شده ، همانیست که در آدرس تگ img  قرار می‌گیره و در ابتدا به کاربر نمایش داده میشه.
برای انجام این عمل من کلاسی را برای کار کردن با عکس برایتان معرفی می‌کنم،  به نام WebImage  که  در داخل فضای نامی System.Web.Helpers  قرار گرفته است.
از طریق این کلاس می‌توان کلیه عملیات دریافت فایل آپلود شده،  ویرایش ، تغییر اندازه ، چرخاندن ، بریدن و حتی watermark کردن و در نهایت ذخیره عکس را به آسانی انجام داد.
من کدهای متد UploadImage را برایتان قرار می‌دهم که زیاد هم بهینه نیست و سپس برایتان توضیح میدهم.

public ActionResult UploadImage(string CKEditorFuncNum, string CKEditor, string langCode)
        {
            string message;  // message to display   when file upload successfully(optional)
            string thumbPath = "";  // the directory for thumb file that should resize
            var db = new MyDbContext();  // make new instance from my context
            // here logic to upload image
            // and get file path of the image
            var file = WebImage.GetImageFromRequest(); // get the uploaded file from request
            var ext = Path.GetExtension(file.FileName);  // get the path of file
            //get the file name without extension
            var fileName = Path.GetFileNameWithoutExtension(file.FileName);
            //add time to file name to avoid same name file overwrite, and then add extension to it
            fileName += DateTime.Now.ToFileTime() + ext;
            //choose the path for the original size of image
            var path = Path.Combine(Server.MapPath("~/Content/UploadedImages/Default"), fileName);
            file.Save(path);  //save the original size of the image
            db.Images.Add(new Image { RealName = file.FileName, FileName = fileName, UploadDate = DateTime.Now }); // save image info to db
            db.SaveChanges();  // submit changes to db
            string defaultPath = "/Content/UploadedImages/Default/" + fileName;  //path for original size of //images
            if (file.Width > 400)  // if width of image bigger than 400 px do resize
            {
                file.Resize(400, 400, true);  //resize the image , third argument is aspect ratio
                string thumbName = "Thumb-" + fileName;  // resized image name
                //path for resized image file
                string path2 = Server.MapPath("~/Content/UploadedImages/Thumbs/" + thumbName);
                thumbPath = "/Content/UploadedImages/Thumbs/" + thumbName;
                //save resized image file
                file.Save(path2);
            }
            else
            {
                thumbPath = defaultPath;  // if the size not bigger than 400px the thumb, path = default path
            }
            // passing message success/failure
            message = "Image was saved correctly";
            // since it is an ajax request it requires this string
            //java script that return files path to ckeditor
            string output = @"<script>window.parent.CKEDITOR.tools.callFunction(" + CKEditorFuncNum + ", \"" + thumbPath + "\", \"" + message + "\");window.parent.document.getElementById('cke_145_textInput').value='" + defaultPath + "';window.parent.document.getElementById('cke_125_textInput').value=0;</script>";
            return Content(output);
        }


توضیحات:
ابتدا یه رشته  به نام message  در نظر گرفتم ، برای هنگامی که آپلود شد، ckeditor به کاربر نشان بده.
سپس منطق من به این صورت بوده که مسیری برای ذخیره سازی فایل‌های تغییر اندازه داده شده ، و نیز مسیری برای فایل‌های با اندازه اصلی در نظر گرفتم.
همچنین من در اینجا من از بانک اطلاعاتی برای ذخیره سازی اطلاعاتی از عکس استفاده کردم،  که در اینجا بحث اصلی ما نیست.
سپس به کمک WebImage.GetImageFromRequest فایل آپلود شده را دریافت کردم.این متد به اندازه کافی باهوش هست که بفهمد ، چه فایلی آپلود شده.
سپس پسوند فایل را از نام فایل جدا کردم، و تاریخ کنونی را به شکل رشته در آورده  و به انتهای نام عکس اضافه کرده تا از تکراری نبودن نام عکس‌ها مطمئن باشم.
سپس پسوند فایل را نیز دوباره به نام فایل اضافه کردم و به کمک متد Save کلاس WebImage عکس را ذخیره کردم.
سپس چک کردم که اگر عرض عکس بیشتر از 400  پیکسل هست ، آن  را تغییر اندازه  بده و ذخیره کنه، و در غیر این صورت آدرس عکسی که قرار بود تغییر اندازه داده بشه با آدرس عکس اصلی یکی میشه.


قسمت مهم:
نکته مهم اینه که ما آدرسهای عکس‌های آپلود شده را چگونه به ckeditor  برگردانیم.
همان طوری که در قسمت آخر هم مشاهده می‌کنید ، ما سه  دستور جاوا اسکریپت به مرورگر برگردوندیمم:
اولیش:

window.parent.CKEDITOR.tools.callFunction(" + CKEditorFuncNum + ", \"" + thumbPath + "\", \"" + message + "\");

در حقیقت ما در اینجا ما از api‌های ckeditor و همچنین پارامترهای ارسالی از طرف ckeditor  استفاده کردیم ، تا قسمت آدرس عکس را با آدرس عکس تغییر اندازه داده شده و کوچک شده پر می‌کنیم.
سوال؟
حالا چگونه قسمت پیوند را پر کنیم؟ این  را دیگر من پیدا نکردم ، تا دست به دامن دوست  و یا شایدم دشمن قدیمیمون جاوا اسکریپت شدم.
اول رفتم به کمک فایرباگ دیدم که id  فیلد پیوند‌ها چیه؟


همان طور که معلومه  id این فیلد cke_145_textInput  هست و به کمک یه خط js می‌توان این فیلد را با آدرس عکس آپلود شده با سایز اصلی پر کرد.
اولش من این را نوشتم:

document.getElementsById("cke_145_textInput").value = defaultPath;

اما بازم js  شروع کرد به بدقلق شدن. هرچی توی کنسول دیباگش کردم خطای null بودن را میداد. بعد از یه ساعت سرو کله زدن و تقریبا ناامید شدن ، چشمم به قسمت اول کد api خود ادیتور که اولش را با window.parent شروع کرده  افتاد ، و من هم کد خودم را به شکل زیر تغییر دادم:

window.parent.document.getElementsById("cke_145_textInput").value = defaultPath;

موفقیت در این قسمت از کد باعث شد که من دست به کد‌تر شوم و مشکل border عکس ها، که به صورت  دیفالت در IE  یا همون دشمن همیشگی وجود داره ، را حل کنم ومقدار border را به صورت پیش فرض صفر کنم. 

window.parent.document.getElementsById('cke_125_textInput').value=0;

خب همه‌ی این دردسر‌ها را ما تحمل کردیم تا به ساده‌ترین شکل ممکن هر عکسی را که آپلود شد ، برای مکانیزم lightbox  آماده کنیم و به راحتی یه گالری عکس خوب داشته باشیم.
خب حتما می‌پرسید که از چه پلاگینی برای ایجاد lightbox  استفاده کنیم:
من به شخصه پلاگین colorbox  را پیشنهاد می‌دم.
با انجام یک سرچ ساده سایتش را پیدا کنید و با مستندات و ویژگی‌های آن آشنا شوید.
یک پیشنهاد:
برای انجام سلکت زدن برای عناصری که باید پلاگین colorbox  روی آنها اعمال شوند من سلکت زدن به شیوه‌ی زیر را پیشنهاد می‌کنم:

$(".container [href$='.jpg']").colorbox({ maxWidth: 800, opacity: 0.5, rel: 'gal' });
$(".container [href$='.png']").colorbox({ maxWidth: 800, opacity: 0.5, rel: 'gal' });
$(".container [href$='.gif']").colorbox({ maxWidth: 800, opacity: 0.5, rel: 'gal' });

فرض کنید div ی که متن و عکس‌های ما را شامل میشه ، کلاسش container  باشه . با کمک [href$='.jpg']  می‌توان گفت هر لینکی که،  پسوند فایلی که به آن اشاره می‌کند، .jpg هست،  ویژگی colorbox  را به خود بگیرید.


یک پیشنهاد برای تشکیل گالری عکس:
همان طور که من در بالا اشاره کردم ، rel را بر روی  gal قرار دادم، تا هر تگی که ویژگی  rel را داشته باشد، تشکیل یک گروه برای گالری عکس را بدهد.
برای اینکه  بتوانیم این ویژگی را به عناصر مورد نظر خود اعمال کنیم ، بازم دست به دامان jQuery می‌شویم:

$(".container [href$='.jpg']").attr("rel", "gal");

خب مثل اینکه دیگر کار تمام شده و امیدوارم برای شما مفید بوده باشه.
موفق باشید... 

مطالب
سازگار کردن لینک‌های قدیمی یک سایت با ساختار جدید آن در ASP.NET MVC
اگر پیشتر سایتی را در آدرس مشخصی در اینترنت داشته‌اید و اکنون تنها نرم افزار آن تغییر کرده است، اما نحوه ارائه خدمات آن خیر، لازم است بتوانید شرایط ذیل را مدیریت کنید:
- موتورهای جستجو مدام اطلاعات قبلی خود را به روز می‌کنند. اگر آدرس قبلی مقاله‌ای در سایت شما http://site/year/month/day/title بوده، برای نمونه گوگل هر از چندگاهی مجددا به این آدرس مراجعه می‌کند تا حداقل مطمئن شود وجود خارجی دارد یا خیر (این نکته را از لاگ‌های خطای سایت استخراج کردم).
- سایت‌های زیادی هستند که پیشتر به سایت شما و مطالب آن لینک داده‌اند. نمی‌توانید از آن‌ها درخواست کنید لطفا بانک اطلاعاتی خود را به روز کنید.
- اگر فید قبلی سایت شما http://site/feeds/posts بوده و اکنون چیز دیگری است، باز هم نمی‌توانید از همه درخواست کنید اطلاعات خود را به روز کنند. عده‌ای اینکار را خواهند کرد و تعداد زیادی هم خیر.

برای مدیریت یک چنین مواردی می‌توان از امکانات مسیریابی موجود در ASP.NET MVC استفاده کرد؛ که نمونه‌ای عملی از آن‌را جهت سازگاری سایت جاری با هاست قبلی آن (بلاگر) در ادامه مطالعه خواهید نمود:

الف) سازگار سازی لینک‌های قدیمی برچسب‌های سایت با ساختار جدید آن
در بلاگر آدرس‌های برچسب‌ها، به صورت http://site/search/label/name تعریف شده است. در سایت جاری برچسب‌ها توسط کنترلر Tag مدیریت می‌شوند. برای هدایت آدرس‌های قدیمی (موجود در موتورهای جستجو یا ثبت شده در سایت‌هایی که به ما لینک داده‌اند) می‌توان از تعریف مسیریابی ذیل در فایل global.asax استفاده کرد:
routes.MapRoute(
                "old_bloger_tags_list", // Route name
                "search/label/{name}", // URL with parameters
                new { controller = "Tag", action = "Index", name = UrlParameter.Optional, area = "" } // Parameter defaults
            );
به این ترتیب به صورت خودکار تمامی آدرس‌های شروع شده با http://site/search/label پالایش شده و سپس قسمت name آن‌ها جدا سازی می‌شود. این نام به متدی به نام Index در کنترلر Tag که دارای پارامتری به نام name است ارسال خواهد شد.

ب) از دست ندادن خوانندگان قدیمی فیدهای سایت
دو نوع فید کلی در بلاگر وجود دارد: http://site/feeds/posts/default و http://site/feeds/comments/default؛ اما در سایت جاری فیدها توسط کنترلری به نام Feed ارائه می‌شوند. برای سازگار سازی آدرس‌های قدیمی و هدایت آن‌ها به صورت خودکار به کنترلر فید می‌توان از دو تعریف مسیریابی ذیل استفاده کرد:
routes.MapRoute(
                "old_bloger_posts_feeds_list", // Route name
                "feeds/posts/default", // URL with parameters
                new { controller = "Feed", action = "Posts", name = UrlParameter.Optional, area = "" } // Parameter defaults
            );

routes.MapRoute(
                "old_bloger_comments_feeds_list", // Route name
                "feeds/comments/default", // URL with parameters
                new { controller = "Feed", action = "Comments", name = UrlParameter.Optional, area = "" } // Parameter defaults
            );            
در اینجا دو آدرس ذکر شده به کنترلر Feed و متدهای Posts و Comments آن هدایت خواهند شد و به این نحو کاربران قدیمی سایت هیچگونه تغییری را احساس نکرده و باز هم فیدخوان‌های آن‌ها، بدون مشکل کار خواهند کرد.

ج) پردازش لینک‌های قدیمی مطالب سایت و هدایت آن‌ها به آدرس‌های جدید
این مورد اندکی مشکل‌تر از موارد قبلی است:
routes.MapRoute(
                "old_bloger_post_urls",
                "{yyyy}/{mm}/{title}",
                new { controller = "Post", action = "OldBloggerLinks" },
                new { yyyy = @"\d{4}", mm = @"\d{1,2}" }
            );
برای نمونه آدرس مقاله‌ای مانند http://site/2012/05/ef-code-first-15.html را درنظر بگیرید. سه قسمت سال، ماه و عنوان آن، حائز اهمیت هستند. این‌ها را در اینجا به کنترلر Post و متد OldBloggerLinks آن هدایت خواهیم کرد. همچنین برای سال و ماه آن نیز قید تعریف شده است. سال عددی 4 رقمی است و ماه عددی یک تا دو رقمی.
کدهای متد OldBloggerLinks را در اینجا مشاهده می‌کنید:
        public virtual ActionResult OldBloggerLinks(int yyyy, int mm, string title)
        {
            var oldUrl = string.Format(CultureInfo.InvariantCulture, "https://www.dntips.ir/{0}/{1}/{2}", yyyy, mm.ToString("00"), title);
            var blogPost = _blogPostsService.FindBlogPost(oldUrl);
            if (blogPost != null)
                return RedirectToActionPermanent(actionName: ActionNames.Index, controllerName: MVC.Post.Name,
                                                 routeValues: new { id = blogPost.Id, name = blogPost.Title.GetPostSlug() });
            return this.Redirect("/");
        }
در اینجا چون ساختار لینک‌ها کلا تغییر کرده است، ابتدا بر اساس پارامترهای دریافت شده، لینک قدیمی بازسازی می‌شود. سپس به بانک اطلاعاتی مراجعه شده و لینک قدیمی به همراه شماره مطلب مرتبط با آن یافت می‌شود (یک فیلد oldUrl برای مطالب قدیمی در بانک اطلاعاتی وجود دارد). در آخر هم به کمک متد  RedirectToActionPermanent آدرس رسیده به آدرس جدید مطلب در سایت ترجمه و هدایت خواهد شد. Permanent بودن آن برای به روز رسانی خودکار اطلاعات موتورهای جستجو مفید است.


نتیجه گیری
به کمک امکانات مسیریابی توکار ASP.NET MVC می‌توان ساختار قدیمی یک سایت را به ساختار جدید آن ترجمه کرد. به این ترتیب لینک‌های قدیمی ثبت شده در صدها سایت اینترنتی که به سایت ما اشاره می‌کنند، مجددا بدون مشکل قابل استفاده بوده و همچنین موتورهای جستجو نیز امکان به روز رسانی اطلاعات خود را خواهند یافت.

مطالب
دریافت اطلاعات بیشتر از Social Provider ها در VS 2013
هنگامی که یک پروژه جدید ASP.NET را در VS 2013 می‌سازید و متد احراز هویت آن را Individual User Accounts انتخاب می‌کنید، قالب پروژه، امکانات لازم را برای استفاده از تامین کنندگان ثالث، فراهم می‌کند، مثلا مایکروسافت، گوگل، توییتر و فیسبوک. هنگامی که توسط یکی از این تامین کننده‌ها کاربری را احراز هویت کردید، می‌توانید اطلاعات بیشتری درخواست کنید. مثلا عکس پروفایل کاربر یا لیست دوستان او. سپس اگر کاربر به اپلیکیشن شما سطح دسترسی کافی داده باشد می‌توانید این اطلاعات را دریافت کنید و تجربه کاربری قوی‌تر و بهتری ارائه کنید.

در این پست خواهید دید که چطور می‌شود از تامین کننده Facebook اطلاعات بیشتری درخواست کرد. پیش فرض این پست بر این است که شما با احراز هویت فیسبوک و سیستم کلی تامین کننده‌ها آشنایی دارید. برای اطلاعات بیشتر درباره راه اندازی احراز هویت فیسبوک به  این لینک  مراجعه کنید.

برای دریافت اطلاعات بیشتر از فیسبوک مراحل زیر را دنبال کنید.

  • یک اپلیکیشن جدید ASP.NET MVC با تنظیمات Individual User Accounts بسازید.
  • احراز هویت فیسبوک را توسط کلید هایی که از Facebook دریافت کرده اید فعال کنید. برای اطلاعات بیشتر در این باره می‌توانید به این لینک مراجعه کنید.
  • برای درخواست اطلاعات بیشتر از فیسبوک، فایل Startup.Auth.cs را مطابق لیست زیر ویرایش کنید.
 List<string> scope = newList<string>() { "email", "user_about_me", "user_hometown", "friends_about_me", "friends_photos" };
 var x = newFacebookAuthenticationOptions();
 x.Scope.Add("email");
 x.Scope.Add("friends_about_me");
 x.Scope.Add("friends_photos");
 x.AppId = "636919159681109";
 x.AppSecret = "f3c16511fe95e854cf5885c10f83f26f";
 x.Provider = newFacebookAuthenticationProvider()
{
    OnAuthenticated = async context =>
    {
         //Get the access token from FB and store it in the database and
        //use FacebookC# SDK to get more information about the user
        context.Identity.AddClaim(
        new System.Security.Claims.Claim("FacebookAccessToken",
                                             context.AccessToken));
    }
};
 x.SignInAsAuthenticationType = DefaultAuthenticationTypes.ExternalCookie;
 app.UseFacebookAuthentication(x);

در خط 1 مشخص می‌کنیم که چه scope هایی از داده را می‌خواهیم درخواست کنیم.
از خط 10 تا 17 رویداد OnAuthenticated را مدیریت می‌کنیم که از طرف Facebook OWIN authentication اجرا می‌شود. این متد هر بار که کاربری با فیسبوک خودش را احراز هویت می‌کند فراخوانی می‌شود. پس از آنکه کاربر احراز هویت شد و به اپلیکیشن سطح دسترسی لازم را اعطا کرد، تمام داده‌ها در FacebookContext ذخیره می‌شوند. 
خط 14 شناسه FacebookAccessToken را ذخیره می‌کند. ما این آبجکت را از فیسبوک دریافت کرده و از آن برای دریافت لیست دوستان کاربر استفاده می‌کنیم.
نکته: در این مثال تمام داده‌ها بصورت Claims ذخیره می‌شوند، اما اگر بخواهید می‌توانید از ASP.NET Identity برای ذخیره آنها در دیتابیس استفاده کنید.
در قدم بعدی لیست دوستان کاربر را از فیسبوک درخواست می‌کنیم. ابتدا فایل Views/Shared/_LoginPartial.cshtml را باز کنید و لینک زیر را به آن بیافزایید.
 <li>
      @Html.ActionLink("FacebookInfo", "FacebookInfo","Account")
</li>

 هنگامی که کاربری وارد سایت می‌شود و این لینک را کلیک می‌کند، ما لیست دوستان او را از فیسبوک درخواست می‌کنیم و بهمراه عکس‌های پروفایل شان آنها را لیست می‌کنیم.
تمام Claim‌ها را از UserIdentity بگیرید و آنها را در دیتابیس ذخیره کنید. در این قطعه کد ما تمام Claim هایی که توسط OWIN دریافت کرده ایم را می‌خوانیم، و شناسه FacebookAccessToken را در دیتابیس عضویت ASP.NET Identity ذخیره می‌کنیم.
//
        // GET: /Account/LinkLoginCallback
        publicasyncTask<ActionResult> LinkLoginCallback()
        {
            var loginInfo = await AuthenticationManager.GetExternalLoginInfoAsync(XsrfKey, User.Identity.GetUserId());
            if (loginInfo == null)
            {
                return RedirectToAction("Manage", new { Message = ManageMessageId.Error });
            }
            var result = await UserManager.AddLoginAsync(User.Identity.GetUserId(), loginInfo.Login);
            if (result.Succeeded)
            {
                var currentUser = await UserManager.FindByIdAsync(User.Identity.GetUserId());
                //Add the Facebook Claim
                await StoreFacebookAuthToken(currentUser);
                return RedirectToAction("Manage");
            }
            return RedirectToAction("Manage", new { Message = ManageMessageId.Error });
        }
خط 14-15 شناسه FacebookAccessToken را در دیتابیس ذخیره می‌کند.
StoreFacebookAuthToken تمام اختیارات (claim)‌های کاربر را از UserIdentity می‌گیرد و Access Token را در قالب یک User Claim در دیتابیس ذخیره می‌کند. اکشن LinkLoginCallback هنگامی فراخوانی می‌شود که کاربر وارد سایت شده و یک تامین کننده دیگر را می‌خواهد تنظیم کند.
اکشن ExternalLoginConfirmation هنگام اولین ورود شما توسط تامین کنندگان اجتماعی مانند فیسبوک فراخوانی می‌شود.
در خط 26 پس از آنکه کاربر ایجاد شد ما یک FacebookAccessToken را بعنوان یک Claim برای کاربر ذخیره می‌کنیم.
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> ExternalLoginConfirmation(ExternalLoginConfirmationViewModel model, string returnUrl)
        {
            if (User.Identity.IsAuthenticated)
            {
                return RedirectToAction("Manage");
            }
 
            if (ModelState.IsValid)
            {
                // Get the information about the user from the external login provider
                var info = await AuthenticationManager.GetExternalLoginInfoAsync();
                if (info == null)
                {
                    return View("ExternalLoginFailure");
                }
                var user = newApplicationUser() { UserName = model.Email };
                var result = await UserManager.CreateAsync(user);
                if (result.Succeeded)
                {
                    result = await UserManager.AddLoginAsync(user.Id, info.Login);
                    if (result.Succeeded)
                    {
                        await StoreFacebookAuthToken(user);
                        await SignInAsync(user, isPersistent: false);
                        return RedirectToLocal(returnUrl);
                    }
                }
                AddErrors(result);
            }
 
            ViewBag.ReturnUrl = returnUrl;
            return View(model);
        }


اکشن ExternalLoginCallback هنگامی فراخوانی می‌شود که شما برای اولین بار یک کاربر را به یک تامین کننده اجتماعی اختصاص می‌دهید. در خط 17 شناسه دسترسی فیسبوک را بصورت یک claim برای کاربر ذخیره می‌کنیم.
//
        // GET: /Account/ExternalLoginCallback
        [AllowAnonymous]
        publicasyncTask<ActionResult> ExternalLoginCallback(string returnUrl)
        {
            var loginInfo = await AuthenticationManager.GetExternalLoginInfoAsync();
            if (loginInfo == null)
            {
                return RedirectToAction("Login");
            }
 
            // Sign in the user with this external login provider if the user already has a login
            var user = await UserManager.FindAsync(loginInfo.Login);
            if (user != null)
            {
                //Save the FacebookToken in the database if not already there
                await StoreFacebookAuthToken(user);
                await SignInAsync(user, isPersistent: false);
                return RedirectToLocal(returnUrl);
            }
            else
            {
                // If the user does not have an account, then prompt the user to create an account
                ViewBag.ReturnUrl = returnUrl;
                ViewBag.LoginProvider = loginInfo.Login.LoginProvider;
                return View("ExternalLoginConfirmation", newExternalLoginConfirmationViewModel { Email = loginInfo.Email });
            }
        }
در آخر شناسه FacebookAccessToken را در دیتابیس ASP.NET Identity ذخیره کنید.
privateasyncTask StoreFacebookAuthToken(ApplicationUser user)
        {
            var claimsIdentity = await AuthenticationManager.GetExternalIdentityAsync(DefaultAuthenticationTypes.ExternalCookie);
            if (claimsIdentity != null)
            {
                // Retrieve the existing claims for the user and add the FacebookAccessTokenClaim
                var currentClaims = await UserManager.GetClaimsAsync(user.Id);
                var facebookAccessToken = claimsIdentity.FindAll("FacebookAccessToken").First();
                if (currentClaims.Count() <=0 )
                {
                    await UserManager.AddClaimAsync(user.Id, facebookAccessToken);
                }

پکیج Facebook C#SDK را نصب کنید. http://nuget.org/packages/Facebook
فایل AccountViewModel.cs را باز کنید و کد زیر را اضافه کنید.
    public class FacebookViewModel
     {
         [Required]
         [Display(Name = "Friend's name")]
         public string Name { get; set; }
  
        public string ImageURL { get; set; }
    }

کد زیر را به کنترلر Account اضافه کنید تا عکس‌های دوستان تان را دریافت کنید.
//GET: Account/FacebookInfo
[Authorize]
publicasyncTask<ActionResult> FacebookInfo()
{
    var claimsforUser = await UserManager.GetClaimsAsync(User.Identity.GetUserId());
    var access_token = claimsforUser.FirstOrDefault(x => x.Type == "FacebookAccessToken").Value;
    var fb = newFacebookClient(access_token);
    dynamic myInfo = fb.Get("/me/friends");
    var friendsList = newList<FacebookViewModel>();
    foreach (dynamic friend in myInfo.data)
    {
        friendsList.Add(newFacebookViewModel()
           {
               Name = friend.name,
               ImageURL = @"https://graph.facebook.com/" + friend.id + "/picture?type=large"
           });
    }
 
    return View(friendsList);
}

در پوشه Views/Account یک نمای جدید با نام FacebookInfo.cshtml بسازید و کد Markup آن را مطابق لیست زیر تغییر دهید.
@model IList<WebApplication96.Models.FacebookViewModel>
 @if (Model.Count > 0)
 {
     <h3>List of friends</h3>
     <div class="row">
             @foreach (var friend in Model)
             {
               <div class="col-md-3">
                <a href="#" class="thumbnail">
                  <img src=@friend.ImageURL alt=@friend.Name />
                 </a>
               </div>
              }
     </div>
 }
در این مرحله، شما می‌توانید لیست دوستان خود را بهمراه عکس‌های پروفایل شان دریافت کنید.
پروژه را اجرا کنید و توسط Facebook وارد سایت شوید. باید به سایت فیسبوک هدایت شوید تا احراز هویت کنید و دسترسی لازم را به اپلیکیشن اعطا کنید. پس از آن مجددا به سایت خودتان باید هدایت شوید.
حال هنگامی که روی لینک FacebookInfo کلیک می‌کنید باید صفحه ای مشابه تصویر زیر ببینید.

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

مطالب
Syntax highlighting در بلاگر!

اگر علاقمند باشید که syntax highlighting را به سورس کدهای ارسالی در بلاگر اضافه کنید، روش کار به صورت زیر است:
از آنجائیکه دسترسی به سرور و راه‌ حل‌های سمت سرور را نخواهیم داشت، تنها راه حل باقیمانده استفاده از روش‌های سمت کلاینت است. کتابخانه زیر این امر را میسر می‌سازد:
http://code.google.com/p/syntaxhighlighter/
این کتابخانه، کار Syntax highlighting سمت کلاینت را با استفاده از JavaScript انجام می‌دهد.

پس از دریافت آن (احتمالا به یک پروکسی نیاز پیدا خواهید کرد ...)، فایل‌ها را در یک سرور قرار دهید. (برای مثال در Google pages)
سپس به قسمت ویرایش html قالب سایت مراجعه کنید و کدهای زیر را به آن اضافه نمائید (درصورت نیاز مسیرهای فایل‌ها را ویرایش کنید):
<link href='http://vahid.nasiri.googlepages.com/SyntaxHighlighter.css' rel='stylesheet' type='text/css'/>
<script src='http://vahid.nasiri.googlepages.com/shCore.js' type='text/javascript'/>
<script src='http://vahid.nasiri.googlepages.com/shBrushCpp.js' type='text/javascript'/>
<script src='http://vahid.nasiri.googlepages.com/shBrushCSharp.js' type='text/javascript'/>
<script src='http://vahid.nasiri.googlepages.com/shBrushCss.js' type='text/javascript'/>
<script src='http://vahid.nasiri.googlepages.com/shBrushJava.js' type='text/javascript'/>
<script src='http://vahid.nasiri.googlepages.com/shBrushJScript.js' type='text/javascript'/>
<script src='http://vahid.nasiri.googlepages.com/shBrushSql.js' type='text/javascript'/>
<script src='http://vahid.nasiri.googlepages.com/shBrushXml.js' type='text/javascript'/>

<script class='javascript'>
//<![CDATA[
function FindTagsByName(container, name, Tag)
{
var elements = document.getElementsByTagName(Tag);
for (var i = 0; i < elements.length; i )
{
if (elements[i].getAttribute("name") == name)
{
container.push(elements[i]);
}
}
}
var elements = [];
FindTagsByName(elements, "code", "pre");
FindTagsByName(elements, "code", "textarea");

for(var i=0; i < elements.length; i ) {
if(elements[i].nodeName.toUpperCase() == "TEXTAREA") {
var childNode = elements[i].childNodes[0];
var newNode = document.createTextNode(childNode.nodeValue.replace(/<br\s*\/?>/gi,'\n'));
elements[i].replaceChild(newNode, childNode);

}
else if(elements[i].nodeName.toUpperCase() == "PRE") {
brs = elements[i].getElementsByTagName("br");
for(var j = 0, brLength = brs.length; j < brLength; j ) {
var newNode = document.createTextNode("\n");
elements[i].replaceChild(newNode, brs[0]);
}
}
}
// dp.SyntaxHighlighter.ClipboardSwf =
//"http://vahid.nasiri.googlepages.com/clipboard.swf";
dp.SyntaxHighlighter.HighlightAll("code");
//]]>
</script>


خطوط فوق باید پس از تگ‌های زیر در قالب استاندارد قرار داده شوند:
</div></div> <!-- end outer-wrapper -->

از این پس جهت استفاده از این قابلیت تنها کافی است از تگ‌های pre یا textarea استفاده کنید (در قسمت html ارسال مطلب) و name را مساوی code قرار داده و language را مساوی زبان مورد نظر. برای مثال:

<div align="left" dir="ltr">
<pre name='code' language='sql'>
--get login time
SELECT login_time FROM master..sysprocesses WHERE spid = 1
</pre>
</div>

که نتیجه نهایی به صورت زیر خواهد بود:
--get login time
SELECT login_time FROM master..sysprocesses WHERE spid = 1

همچنین باید دقت داشت که مجاز به ارسال کاراکترهای غیرمجاز در xml (<>\'&) در کدهای خود نیستید و این کاراکترها سبب خواهند شد که کد شما نمایش داده نشوند. به همین جهت همانطور که پیشتر نیز ذکر شد می‌توان از سرویس سایت http://www.elliotswan.com/postable/ استفاده کرد.

ماخذ:
http://developertips.blogspot.com/2007/08/syntaxhighlighter-on-blogger.html



مطالب
شروع کار با Apache Cordova در ویژوال استودیو #5

همانطور که در قسمت قبل گفته شد، در این قسمت با روش کار jQuery Mobile و plugin‌های مربوط به Cordova آشنا خواهیم شد.


تگ متای زیر برای تنظیمات مربوط به viewport است و برای jQuery Mobile توصیه می‌شود.
<!DOCTYPE html> 
<html> 
<head> 
<meta charset="utf-8"> 
<title>Title</title> 
<meta name="viewport" content="width=device-width, initial-scale=1">
device-width  نشان می‌دهد که می‌خواهیم مقیاس محتوای ما به اندازه‌ی عرض دستگاه(device) مورد نظر باشد و initial-scale هم مقدار زوم را برای Web page ما مشخص می‌کند. شما می‌توانید با مقدار دهی user-scalable=no هم امکان تغییر زوم را به کاربر ندهید. این متا تگ را در تمام صفحات html خود بعد از تگ title قرار دهید.

روال کار jQuery Mobile
برای اینکه بتواند سند HTML ما را برای استفاده‌ی در موبایل بهینه کند، ابتدا آن را لود می‌کند و سپس بر  اجزایی که با ویژگیdata-role علامت گذاری شده‌اند، CSS3 بهینه شده برای موبایل را اعمال می‌کند.


از آنجایی که مستندات jQuery Mobile به قدر کافی کامل هست، نیازی نیست تا در مورد تک تک آنها مثال بزنیم و از اصل مطلب دور شویم. در هر مثالی که زده خواهد شد، در صورت استفاده از ویجتی خاص، با آن آشنا خواهیم شد.

لیست کامل اتریبیوت‌های -data به همراه مقادیری که می‌پذیرند 

دموی مربوط به ویجت‌ها  

لیست تمام رخدادها 

شما می‌توانید از امکانات Theme Roller برای شخصی سازی تم‌های مورد نیاز استفاده کنید.

لیست کامل کلاس‌های CSS  



Cordova Plugins

از این قسمت http://plugins.cordova.io/#/viewAll و این قسمت  http://plugreg.com/plugins می‌توانید سراغ پلاگین‌های مورد نیاز خود بگردید. برای مثال وارد بخش کانفیگ پروژه شده و از قسمت plugins  و تب Core یکسری از پلاگین‌هایی را که در Cordova گنجانده شده است، مشاهده می‌کنید. با کلیک بر روی دکمه‌ی Add می‌توانید آن را دانلود کرده و از API‌های آن استفاده کنید.



برای مثال پلاگین Notification را به پروژه اضافه می‌کنم. سپس یک فایل js را با نام custom.js به فولدر scripts در ریشه پروژه اضافه کرده و  محتوای فایل‌های index.html , custome.js را به شکل زیر در نظر می‌گیرم:


$(function() {
    $("#alert").on('tap', function(event) {
        navigator.notification.alert("اطلاعات ذخیره شد",null, "alert", "تایید");
    });

    $("#prompt").on('tap', function(event) {
        navigator.notification.prompt("برای تائید نام خود را وارد کنید", onPrompt, "prompt", "تایید", "لغو"],"نام خود"]);
    });

    function onPrompt(results) {
        navigator.notification.alert(results.buttonIndex + "\n" + results.input1, null);
    }
    $("#confirm").on('tap', function(event) {
        navigator.notification.confirm("حذف انجام شود؟", onConfirm, "confirm", ["بله", "خیر", "نمیدانم"]);
    });

    function onConfirm(buttonIndex) {
        navigator.notification.alert(buttonIndex , null);
    }
    $("#beep").on('tap', function(event) {
        navigator.notification.beep(1);
    });

});

رخداد tap زمانی صادر می‌شود که کاربر، دکمه‌ی مورد نظر را لمس کند و یکی از رخداد‌های jQuery Mobile می‌باشد. بعد از نصب پلاگین Notification، با استفاده از navigator.notification می‌توانید به متد‌های مورد نظر که در بالا مشخص است، دسترسی پیدا کنید.

برای آشنایی با این پلاگین می‌توانید داکیومنت آن را مطالعه کنید.

در کد بالا با استفاده از متد‌های callback توانسته‌ایم اطلاعاتی در مورد نوع عملکرد کاربر با notification ما بدست آوریم.


<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title>CordovaApp01</title>
   <meta name="viewport" content="width=device-width, initial-scale=1"/> 
    <!-- CordovaApp01 references -->
    <link href="css/index.css" rel="stylesheet" />
    <link href="jquery.mobile.rtl/css/themes/default/rtl.jquery.mobile-1.4.0.css" rel="stylesheet" />
</head>
<body>
<div data-role="page" id="page1">
    <div data-role="header">
        <h2>
            تست پلاگین Notification
        </h2>
    </div>
    <div data-role="content">
        <a href="#page2" data-transition="pop" data-rel="dialog" data-role="button" data-inline="true" data-icon="back">page 2</a>
       
        <button data-role="button" id="alert" data-inline="true" >alert</button>
        <button data-role="button" id="confirm" data-inline="true">confirm</button>
        <button data-role="button" id="beep" data-inline="true" >beep</button>
        <button data-role="button" id="prompt" data-inline="true" >prompt</button>

    </div>
    <div data-role="footer">
        <h2>من فوتر هستم</h2>
    </div>
</div>
    <div data-role="page" id="page2">
        <div data-role="header">
            <h1>Header</h1>
        </div>
        <div data-role="content">
            Content
        </div>
        <div data-role="footer">
            <h1>Footer</h1>
        </div>
    </div>
<!-- Cordova reference, this is added to your app when it's built. -->
    <script src="scripts/jquery-2.1.3.min.js"></script>
    <script src="cordova.js"></script>
    <script src="scripts/platformOverrides.js"></script>
    <script src="scripts/index.js"></script>
    <script src="jquery.mobile.rtl/js/rtl.jquery.mobile-1.4.0.js"></script>
    <script src="scripts/custom.js"></script>
</body>
</html>

در کد بالا 4 تا button دیده می‌شود که ویژگی data-role آنها مقدار button در نظر گرفته شده‌است تا توسط jQuery Mobile به عنوان button شناخته شوند و استایل‌های لازم بر روی آن‌ها اعمال گردد. قرار است طبق کد js ایی که نوشته‌ایم، با لمس کردن هر کدام از دکمه‌ها، notification هایی نمایش داده شوند.


برای اینکار شبیه ساز YouWave را دانلود کرده و نصب کنید. سپس در قسمت toolbar ویژوال، گزینه‌ی Device را به جای شبیه ساز Ripple انتخاب کنید. نرم افزار youwave را اجرا کنید حال اگر برنامه را اجرا کنید با خطای زیر مواجه خواهید شد:

Error447C:\Users\Administrator\Documents\Visual Studio 2013\Projects\CordovaApp-01\CordovaApp-01\bld\Debug\platforms\android\cordova\node_modules\q\q.js:126CordovaApp-01
Error448throw e;CordovaApp-01
Error449^CordovaApp-01
Error450Error : DEP10201 : Failed to deploy to device, no devices found.CordovaApp-01
مشخصا خطا، مبنی بر پیدا نشدن دستگاه خارجی است. برای رفع این مشکل می‌بایست شبیه ساز youwave را به ویژوال استودیو وصل کنیم. برای این منظور دستور زیر را در cmd اجرا کنید.
adb connect localhost:5558

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


علاوه بر این ما در سند HTML خود در بالا، یک page و یک تگ a قرار داده‌ایم. 
 <a href="#page2" data-transition="pop" data-rel="dialog" data-role="button" data-inline="true" data-icon="back">page 2</a>
data-role: با مقدار button در نظر گرفته شده است؛ لذا به شکل 4 دکمه دیگر رندر خواهد شد.
data-transition: با مقدار pop در نظر گرفته شده است که مشخص کننده‌ی افکت ظاهر شدن صفحه‌ای است که قرار است بار گذاری شود.
data-rel: مشخص می‌کند که صفحه‌ی مورد نظر من به صورت دیالوگ باز شود.
data-icon: با استفاده از این ویژگی می‌توان icon مورد نظر خود را برای المنت در نظر گرفت.
data-inline: برای به خط کردن دکمه‌ها کنار هم استفاده می‌شود.
با لمس کردن این دکمه، نتیجه به شکل زیر خواهد بود:

در مقاله‌ی بعد، به مباحث Database در Cordova خواهیم پرداخت.

ادامه دارد...

مطالب
گوگل ریدر و افزودن توضیحات

اگر به گوگل ریدر دقت کرده باشید، دو گزینه‌ی به اشتراک گذاری دارد: share و share with note .


اگر گزینه‌ی share with note را انتخاب کرده و توضیحی را ارسال یا اضافه کنیم، این توضیحات، به فید از نوع Atom اشتراک‌ها هم اضافه می‌شود. مثلا:

<?xml version="1.0"?>
<feed xmlns:media="http://search.yahoo.com/mrss/"
xmlns:gr="http://www.google.com/schemas/reader/atom/"
xmlns:idx="urn:atom-extension:indexing"
xmlns="http://www.w3.org/2005/Atom"
idx:index="no"
gr:dir="ltr">

...

<entry gr:crawl-timestamp-msec="1316627782108">
...
<gr:annotation>
<content type="html">text-text-text</content>
<author>
<name>Vahid</name>
</author>
</gr:annotation>
...
</entry>

...

</feed>



این افزونه استاندارد نیست و همانطور که در قسمت xmlns:gr اطلاعات فوق مشخص است، در فضای نام http://www.google.com/schemas/reader/atom/ معنا پیدا می‌کند. از دات نت سه و نیم به بعد هم کلاسی جهت خواندن فیدهای استاندارد وجود دارد (تعریف شده در فضای نام System.ServiceModel.Syndication). اما چگونه می‌توان این افزونه‌ی غیر استاندارد را با کمک امکانات توکار دات نت خواند؟
روش کار با استفاده از ElementExtensions هر آیتم یک فید است؛ به صورت زیر :

using System;
using System.Collections.Generic;
using System.Linq;
using System.ServiceModel.Syndication;
using System.Xml;
using System.Xml.Linq;

namespace Linq2Rss
{
public class RssEntry
{
public string Title { set; get; }
public string Description { set; get; }
public string Link { set; get; }
public DateTime PublicationDate { set; get; }
public string Author { set; get; }
public string BlogName { set; get; }
public string BlogAddress { set; get; }
public string Annotation { set; get; }
}

public static class AtomReader
{
private static string getAtomAnnotation(this SyndicationElementExtensionCollection items)
{
if (!items.Any()) return string.Empty;
var item = items.Where(x => x.OuterName.ToLowerInvariant() == "annotation").FirstOrDefault();
if (item == null) return string.Empty;

var element = item.GetObject<XElement>();
var content = element.Element("{http://www.w3.org/2005/Atom}content");
return content == null ? string.Empty : content.Value;
}

public static IList<RssEntry> GetEntries(string feedUrl)
{
using (var reader = XmlReader.Create(feedUrl))
{
var feed = SyndicationFeed.Load(reader);
if (feed == null) return null;

return feed.Items.Select(x =>
new RssEntry
{
Title = x.Title.Text,
Author = x.Authors.Any() ? x.Authors.First().Name : string.Empty,
Description = x.Content == null ? string.Empty : ((TextSyndicationContent)x.Content).Text,
Link = x.Links.Any() ? x.Links.First().Uri.AbsoluteUri : string.Empty,
PublicationDate = x.PublishDate.UtcDateTime,
BlogName = x.SourceFeed.Title.Text,
BlogAddress = x.SourceFeed.Links.Any() ? x.SourceFeed.Links.First().Uri.AbsoluteUri : string.Empty,
Annotation = x.ElementExtensions.getAtomAnnotation()

}).ToList();
}
}
}
}

در این مثال به کمک متد الحاقی getAtomAnnotation، مجموعه‌ی SyndicationElementExtensionCollection هر آیتم یک فید بررسی شده، در بین این‌ها، موردی که از نوع annotation باشد انتخاب و سپس content آن استخراج می‌گردد.


نکته‌ای دیگر:
اکثر کلاس‌های موجود در فضاهای نام مرتبط با XML در دات نت امکان خواندن اطلاعات را از یک Uri هم دارند؛ مانند مثال فوق و متد XmlReader.Create بکارگرفته شده در آن. اما اگر بخواهیم حین خواندن اطلاعات، یک پروکسی را نیز به پروسه جاری اضافه کنیم، به نظر خاصیت یا متدی جهت انجام اینکار وجود ندارد. برای رفع این مشکل می‌توان یک پروکسی سراسری را تعریف کرد. تنها کافی است خاصیت System.Net.WebRequest.DefaultWebProxy مقدار دهی شود. پس از آن به صورت خودکار بر روی کل برنامه تاثیر خواهد گذاشت.


مطالب
تغییر ابعاد یک TextArea با استفاده از jQuery

اگر به سایت stackoverflow دقت کنید، اندازه textarea ایی که کاربران امکان ارسال مطلب دارند، قابل تغییر است:



شاید برای شما جالب باشد که بدانید به چه صورتی اینکار را انجام داده‌اند. اینکار با استفاده از افزونه TextArea Resizer صورت گرفته است. (دریافت کتابخانه به همراه مثال)

البته حالت عمومی‌تری نیز توسط jQuery-UI استاندارد پشتیبانی می‌شود (امکان تغییر اندازه یک المان با قابلیت تغییر اندازه در حالت کلی). برای مثال به صفحه‌ی ساده‌ی ASP.Net زیر دقت بفرمائید:

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="WebForm5.aspx.cs" Inherits="testWebForms87.WebForm5" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title></title>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.2.6/jquery.min.js" type="text/javascript"></script>
<script src="http://ajax.googleapis.com/ajax/libs/jqueryui/1.5.3/jquery-ui.min.js" type="text/javascript"></script>

<script type="text/javascript">
$(function() {
$("#resizableArea").resizable({
handles: "s"
})
.find("textarea").height("100%").width("100%");
});
</script>

</head>
<body>
<form id="form1" runat="server">
<div id="resizableArea" style="width:300px; height:200px" >
<asp:TextBox ID="TextBox1" runat="server" TextMode="MultiLine" ></asp:TextBox>
</div>
</form>
</body>
</html>

در اینجا با استفاده از jQuery-UI ابتدا div ایی با id مساوی resizableArea پیدا می‌شود و تابع resizable به آن اعمال می‌گردد. سپس در این div ، تمامی عناصر textarea موجود یافت شده و طول و عرض آن‌ها به اندازه‌ی جدید تغییر یافته div تنظیم خواهد شد. در مورد تنظیم‌های دیگری مانند نمایش ناحیه تغییر یافته به صورت animation می‌توان به راهنمای آن مراجعه نمود.
برای مثال با استفاده از این روش می‌توان یک GridView با قابلیت تغییر اندازه ایجاد کرد و امثال آن. یا برای نمونه شاید با مثال‌هایی که به گرید نمایش داده شده اسکرول بار اضافه می‌کنند برخورده باشید:

<div id="resizableArea" style="overflow:auto;height:200px;">
My Grid view ...
</div>

و یا استفاده از یک پنل:

<asp:Panel ID="pnlScroll" runat="server" Width="391px" Height="282px" ScrollBars="Vertical">
My Grid view ...
</asp:Panel>

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

مطالب
امن سازی برنامه‌های ASP.NET Core توسط IdentityServer 4x - قسمت دوازدهم- یکپارچه سازی با اکانت گوگل
در مطلب قبلی «استفاده از تامین کننده‌های هویت خارجی»، نحوه‌ی استفاده از اکانت‌های ویندوزی کاربران یک شبکه، به عنوان یک تامین کننده‌ی هویت خارجی بررسی شد. در ادامه می‌خواهیم از اطلاعات اکانت گوگل و IDP مبتنی بر OAuth 2.0 آن به عنوان یک تامین کننده‌ی هویت خارجی دیگر استفاده کنیم.


ثبت یک برنامه‌ی جدید در گوگل

اگر بخواهیم از گوگل به عنوان یک IDP ثالث در IdentityServer استفاده کنیم، نیاز است در ابتدا برنامه‌ی IDP خود را به آن معرفی و در آنجا ثبت کنیم. برای این منظور مراحل زیر را طی خواهیم کرد:

1- مراجعه به developer console گوگل و ایجاد یک پروژه‌ی جدید
https://console.developers.google.com
در صفحه‌ی باز شده، بر روی دکمه‌ی select project در صفحه و یا لینک select a project در نوار ابزار آن کلیک کنید. در اینجا دکمه‌ی new project و یا create را مشاهده خواهید کرد. هر دوی این مفاهیم به صفحه‌ی زیر ختم می‌شوند:


در اینجا نامی دلخواه را وارد کرده و بر روی دکمه‌ی create کلیک کنید.

2- فعالسازی API بر روی این پروژه‌ی جدید


در ادامه بر روی لینک Enable APIs And Services کلیک کنید و سپس google+ api را جستجو نمائید.
پس از ظاهر شدن آن، این گزینه را انتخاب و در صفحه‌ی بعدی، آن‌را با کلیک بر روی دکمه‌ی enable، فعال کنید.

3- ایجاد credentials


در اینجا بر روی دکمه‌ی create credentials کلیک کرده و در صفحه‌ی بعدی، این سه گزینه را با مقادیر مشخص شده، تکمیل کنید:
• Which API are you using? – Google+ API
• Where will you be calling the API from? – Web server (e.g. node.js, Tomcat)
• What data will you be accessing? – User data
سپس در ذیل این صفحه بر روی دکمه‌ی «What credentials do I need» کلیک کنید تا به صفحه‌ی پس از آن هدایت شوید. اینجا است که مشخصات کلاینت OAuth 2.0 تکمیل می‌شوند. در این صفحه، سه گزینه‌ی آن‌را به صورت زیر تکمیل کنید:
• نام: همان مقدار پیش‌فرض آن
• Authorized JavaScript origins: آن‌را خالی بگذارید.
• Authorized redirect URIs: این مورد همان callback address مربوط به IDP ما است که در اینجا آن‌را با آدرس زیر مقدار دهی خواهیم کرد.
https://localhost:6001/signin-google
این آدرس، به آدرس IDP لوکال ما اشاره می‌کند و مسیر signin-google/ آن باید به همین نحو تنظیم شود تا توسط برنامه شناسایی شود.
سپس در ذیل این صفحه بر روی دکمه‌ی «Create OAuth 2.0 Client ID» کلیک کنید تا به صفحه‌ی «Set up the OAuth 2.0 consent screen» بعدی هدایت شوید. در اینجا دو گزینه‌ی آن‌را به صورت زیر تکمیل کنید:
- Email address: همان آدرس ایمیل واقعی شما است.
- Product name shown to users: یک نام دلخواه است. نام برنامه‌ی خود را برای نمونه ImageGallery وارد کنید.
برای ادامه بر روی دکمه‌ی Continue کلیک نمائید.

4- دریافت credentials
در پایان این گردش کاری، به صفحه‌ی نهایی «Download credentials» می‌رسیم. در اینجا بر روی دکمه‌ی download کلیک کنید تا ClientId  و ClientSecret خود را توسط فایلی به نام client_id.json دریافت نمائید.
سپس بر روی دکمه‌ی Done در ذیل صفحه کلیک کنید تا این پروسه خاتمه یابد.


تنظیم برنامه‌ی IDP برای استفاده‌ی از محتویات فایل client_id.json

پس از پایان عملیات ایجاد یک برنامه‌ی جدید در گوگل و فعالسازی Google+ API در آن، یک فایل client_id.json را دریافت می‌کنیم که اطلاعات آن باید به صورت زیر به فایل آغازین برنامه‌ی IDP اضافه شود:
الف) تکمیل فایل src\IDP\DNT.IDP\appsettings.json
{
  "Authentication": {
    "Google": {
      "ClientId": "xxxx",
      "ClientSecret": "xxxx"
    }
  }
}
در اینجا مقادیر خواص client_secret و client_id موجود در فایل client_id.json دریافت شده‌ی از گوگل را به صورت فوق به فایل appsettings.json اضافه می‌کنیم.
ب) تکمیل اطلاعات گوگل در کلاس آغازین برنامه
namespace DNT.IDP
{
    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
   // ... 

            services.AddAuthentication()
                .AddGoogle(authenticationScheme: "Google", configureOptions: options =>
                {
                    options.SignInScheme = IdentityServerConstants.ExternalCookieAuthenticationScheme;
                    options.ClientId = Configuration["Authentication:Google:ClientId"];
                    options.ClientSecret = Configuration["Authentication:Google:ClientSecret"];
                });
        }
خود ASP.NET Core از تعریف اطلاعات اکانت‌های Google, Facebook, Twitter, Microsoft Account و  OpenID Connect پشتیبانی می‌کند که در اینجا نحوه‌ی تنظیم اکانت گوگل آن‌را مشاهده می‌کنید.
- authenticationScheme تنظیم شده باید یک عبارت منحصربفرد باشد.
- همچنین SignInScheme یک چنین مقداری را در اصل دارد:
 public const string ExternalCookieAuthenticationScheme = "idsrv.external";
از این نام برای تشکیل قسمتی از نام کوکی که اطلاعات اعتبارسنجی گوگل در آن ذخیره می‌شود، کمک گرفته خواهد شد.


آزمایش اعتبارسنجی کاربران توسط اکانت گوگل آن‌ها

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


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


پس از آن، از طرف گوگل به صورت خودکار به IDP (همان آدرسی که در فیلد Authorized redirect URIs وارد کردیم)، هدایت شده و callback رخ‌داده، ما را به سمت صفحه‌ی ثبت اطلاعات کاربر جدید هدایت می‌کند. این تنظیمات را در قسمت قبل ایجاد کردیم:
namespace DNT.IDP.Controllers.Account
{
    [SecurityHeaders]
    [AllowAnonymous]
    public class ExternalController : Controller
    {
        public async Task<IActionResult> Callback()
        {
            var result = await HttpContext.AuthenticateAsync(IdentityServer4.IdentityServerConstants.ExternalCookieAuthenticationScheme);
            var returnUrl = result.Properties.Items["returnUrl"] ?? "~/";

            var (user, provider, providerUserId, claims) = await FindUserFromExternalProvider(result);
            if (user == null)
            {
                // user = AutoProvisionUser(provider, providerUserId, claims);
                
                var returnUrlAfterRegistration = Url.Action("Callback", new { returnUrl = returnUrl });
                var continueWithUrl = Url.Action("RegisterUser", "UserRegistration" ,
                    new { returnUrl = returnUrlAfterRegistration, provider = provider, providerUserId = providerUserId });
                return Redirect(continueWithUrl);
            }
اگر خروجی متد FindUserFromExternalProvider آن null باشد، یعنی کاربری که از سمت تامین کننده‌ی هویت خارجی/گوگل به برنامه‌ی ما وارد شده‌است، دارای اکانتی در سمت IDP نیست. به همین جهت او را به سمت صفحه‌ی ثبت نام کاربر هدایت می‌کنیم.
در اینجا نحوه‌ی اصلاح اکشن متد Callback را جهت هدایت یک کاربر جدید به صفحه‌ی ثبت نام و تکمیل اطلاعات مورد نیاز IDP را مشاهده می‌کنید.
returnUrl ارسالی به اکشن متد RegisterUser، به همین اکشن متد جاری اشاره می‌کند. یعنی کاربر پس از تکمیل اطلاعات و اینبار نال نبودن user او، گردش کاری جاری را ادامه خواهد داد و به برنامه با این هویت جدید وارد می‌شود.


اتصال کاربر وارد شده‌ی از طریق یک IDP خارجی به اکانتی که هم اکنون در سطح IDP ما موجود است

تا اینجا اگر کاربری از طریق یک IDP خارجی به برنامه وارد شود، او را به صفحه‌ی ثبت نام کاربر هدایت کرده و پس از دریافت اطلاعات او، اکانت خارجی او را به اکانتی جدید که در IDP خود ایجاد می‌کنیم، متصل خواهیم کرد. به همین جهت بار دومی که این کاربر به همین ترتیب وارد سایت می‌شود، دیگر صفحه‌ی ثبت نام و تکمیل اطلاعات را مشاهده نمی‌کند. اما ممکن است کاربری که برای اولین بار از طریق یک IDP خارجی به سایت ما وارد شده‌است، هم اکنون دارای یک اکانت دیگری در سطح IDP ما باشد؛ در اینجا فقط اتصالی بین این دو صورت نگرفته‌است. بنابراین در این حالت بجای ایجاد یک اکانت جدید، بهتر است از همین اکانت موجود استفاده کرد و صرفا اتصال UserLogins او را تکمیل نمود.
به همین جهت ابتدا نیاز است لیست Claims بازگشتی از گوگل را بررسی کنیم:
var (user, provider, providerUserId, claims) = await FindUserFromExternalProvider(result);  
foreach (var claim in claims)
{
   _logger.LogInformation($"External provider[{provider}] info-> claim:{claim.Type}, value:{claim.Value}");
}
در اینجا پس از فراخوانی FindUserFromExternalProvider، لیست Claims بازگشت داده شده‌ی توسط IDP خارجی را لاگ می‌کنیم که در حالت استفاده‌ی از گوگل چنین خروجی را دارد:
External provider[Google] info-> claim:http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name, value:Vahid N.
External provider[Google] info-> claim:http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname, value:Vahid
External provider[Google] info-> claim:http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname, value:N.
External provider[Google] info-> claim:urn:google:profile, value:https://plus.google.com/105013528531611201860
External provider[Google] info-> claim:http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress, value:my.name@gmail.com
بنابراین اگر بخواهیم بر اساس این claims بازگشتی از گوگل، کاربر جاری در بانک اطلاعاتی خود را بیابیم، فقط کافی است اطلاعات claim مخصوص emailaddress آن‌را مورد استفاده قرار دهیم:
        [HttpGet]
        public async Task<IActionResult> Callback()
        {
            // ...

            var (user, provider, providerUserId, claims) = await FindUserFromExternalProvider(result);
            if (user == null)
            {
                // user wasn't found by provider, but maybe one exists with the same email address?  
                if (provider == "Google")
                {
                    // email claim from Google
                    var email = claims.FirstOrDefault(c =>
                        c.Type == "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress");
                    if (email != null)
                    {
                        var userByEmail = await _usersService.GetUserByEmailAsync(email.Value);
                        if (userByEmail != null)
                        {
                            // add Google as a provider for this user
                            await _usersService.AddUserLoginAsync(userByEmail.SubjectId, provider, providerUserId);

                            // redirect to ExternalLoginCallback
                            var continueWithUrlAfterAddingUserLogin =
                                Url.Action("Callback", new {returnUrl = returnUrl});
                            return Redirect(continueWithUrlAfterAddingUserLogin);
                        }
                    }
                }


                var returnUrlAfterRegistration = Url.Action("Callback", new {returnUrl = returnUrl});
                var continueWithUrl = Url.Action("RegisterUser", "UserRegistration",
                    new {returnUrl = returnUrlAfterRegistration, provider = provider, providerUserId = providerUserId});
                return Redirect(continueWithUrl);
            }
در اینجا ابتدا بررسی شده‌است که آیا کاربر جاری واکشی شده‌ی از بانک اطلاعاتی نال است؟ اگر بله، اینبار بجای هدایت مستقیم او به صفحه‌ی ثبت کاربر و تکمیل مشخصات او، مقدار email این کاربر را از لیست claims بازگشتی او از طرف گوگل، استخراج می‌کنیم. سپس بر این اساس اگر کاربری در بانک اطلاعاتی وجود داشت، تنها اطلاعات تکمیلی UserLogin او را که در اینجا خالی است، به اکانت گوگل او متصل می‌کنیم. به این ترتیب دیگر کاربر نیازی نخواهد داشت تا به صفحه‌ی ثبت اطلاعات تکمیلی هدایت شود و یا اینکه بی‌جهت رکورد User جدیدی را مخصوص او به بانک اطلاعاتی اضافه کنیم.



کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید.
برای اجرای برنامه:
- ابتدا به پوشه‌ی src\WebApi\ImageGallery.WebApi.WebApp وارد شده و dotnet_run.bat آن‌را اجرا کنید تا WebAPI برنامه راه اندازی شود.
- سپس به پوشه‌ی src\IDP\DNT.IDP مراجعه کرده و و dotnet_run.bat آن‌را اجرا کنید تا برنامه‌ی IDP راه اندازی شود.
- در آخر به پوشه‌ی src\MvcClient\ImageGallery.MvcClient.WebApp وارد شده و dotnet_run.bat آن‌را اجرا کنید تا MVC Client راه اندازی شود.
اکنون که هر سه برنامه در حال اجرا هستند، مرورگر را گشوده و مسیر https://localhost:5001 را درخواست کنید. در صفحه‌ی login نام کاربری را User 1 و کلمه‌ی عبور آن‌را password وارد کنید.