مطالب
تبدیل PDF به تصویر با استفاده از API توکار Window 8.1 در برنامه‌های غیر مترو دات نت
ویندوز 8.1 دارای امکانات و API توکاری جهت نمایش و خواندن فایل‌های PDF در برنامه‌های مترو است. در ادامه قصد داریم از این امکانات در یک برنامه‌ی متداول دات نت، برای مثال یک برنامه‌ی کنسول غیر مترو استفاده کنیم.


آماده سازی برنامه‌های دات نت برای دسترسی به API مترو ویندوز 8.1

ابتدا یک برنامه‌ی کنسول دات نت 4.5.1 را آغاز کنید. برای دسترسی به API ویندوز 8.1 حتما نیاز است که حداقل از دات نت 4.5.1 شروع کرد. سپس برنامه را در VS.NET بسته و فایل پروژه آن‌را در یک ادیتور متنی باز کنید.
در ابتدای فایل csproj، نیاز است سطر TargetPlatformVersion ذیل اضافه شود.
  <PropertyGroup>
    <TargetFrameworkVersion>v4.5.1</TargetFrameworkVersion>
    <TargetPlatformVersion>8.1</TargetPlatformVersion>
  </PropertyGroup>
سپس در همین فایل، ارجاعات زیر را نیز اضافه نمائید:
  <ItemGroup>
    <Reference Include="System" />
    <Reference Include="System.ComponentModel.DataAnnotations" />
    <Reference Include="System.Core" />
    <Reference Include="System.ObjectModel" />
    <Reference Include="System.Xml.Linq" />
    <Reference Include="System.Data.DataSetExtensions" />
    <Reference Include="Microsoft.CSharp" />
    <Reference Include="System.Data" />
    <Reference Include="System.Xml" />
    <Reference Include="System.Threading" />
    <Reference Include="System.Threading.Tasks" />
  </ItemGroup>
  <ItemGroup>
    <Reference Include="Windows" />
    <Reference Include="System.Runtime" />
    <Reference Include="System.Runtime.WindowsRuntime" />
  </ItemGroup>
مواردی مانند System.Runtime، System.Runtime.WindowsRuntime امکان دسترسی به API ویندوز 8 را در برنامه‌های دات نت میسر می‌کنند.


یک نکته
اگر می‌خواهید این فرآیند را ساده و خودکار کنید، از قالب‌های پروژه‌ی مخصوص DesktopWinRT.Templates.vsix استفاده نمائید.
DesktopWinRT.Templates.vsix


افزودن ارجاعی به Nito.AsyncEx

چون برنامه‌ی مورد استفاده کنسول است و API ویندوز 8 کاملا async طراحی شده‌است، نیاز است با کمک AsyncContext موجود در کتابخانه‌ی Nito.AsyncEx بتوان از امکانات async و await در متد Main برنامه استفاده کرد. البته اگر از سایر برنامه‌های دسکتاپ استفاده می‌کنید، فقط کافی است امضای متد رخدادن گردان را به async تغییر دهید.
 install-package Nito.AsyncEx


تبدیل استریم‌های دات نت به استریم‌های WinRT

اکثر متدهای WinRT با استریم‌هایی از نوع IRandomAccessStream کار می‌کنند. برای اینکه بتوان استریم استاندارد دات نت را به این نوع تبدیل کرد، می‌توان از کلاس‌های ذیل کمک گرفت:
using System;
using System.IO;
using Windows.Storage.Streams;

namespace ConsoleWin81PdfApiTest
{
    public static class MicrosoftStreamExtensions
    {
        public static IRandomAccessStream AsRandomAccessStream(this Stream stream)
        {
            return new RandomStream(stream);
        }

    }

    class RandomStream : IRandomAccessStream
    {
        readonly Stream _internstream;

        public RandomStream(Stream underlyingstream)
        {
            _internstream = underlyingstream;
        }

        public IInputStream GetInputStreamAt(ulong position)
        {
            _internstream.Position = (long)position;
            return _internstream.AsInputStream();
        }

        public IOutputStream GetOutputStreamAt(ulong position)
        {
            _internstream.Position = (long)position;
            return _internstream.AsOutputStream();
        }

        public ulong Size
        {
            get
            {
                return (ulong)_internstream.Length;
            }
            set
            {
                _internstream.SetLength((long)value);
            }
        }

        public bool CanRead
        {
            get { return _internstream.CanRead; }
        }

        public bool CanWrite
        {
            get { return _internstream.CanWrite; }
        }

        public IRandomAccessStream CloneStream()
        {
            throw new NotSupportedException();
        }

        public ulong Position
        {
            get { return (ulong)_internstream.Position; }
        }

        public void Seek(ulong position)
        {
            _internstream.Seek((long)position, SeekOrigin.Begin);
        }

        public void Dispose()
        {
            _internstream.Dispose();
        }

        public Windows.Foundation.IAsyncOperationWithProgress<IBuffer, uint> ReadAsync(IBuffer buffer, uint count, InputStreamOptions options)
        {
            return GetInputStreamAt(Position).ReadAsync(buffer, count, options);
        }

        public Windows.Foundation.IAsyncOperation<bool> FlushAsync()
        {
            return GetOutputStreamAt(Position).FlushAsync();
        }

        public Windows.Foundation.IAsyncOperationWithProgress<uint, uint> WriteAsync(IBuffer buffer)
        {
            return GetOutputStreamAt(Position).WriteAsync(buffer);
        }
    }
}
تا اینجا به یک متد الحاقی جدیدی به نام AsRandomAccessStream می‌رسیم که امکان تبدیل استریم استاندارد دات نت را به IRandomAccessStream مخصوص WinRT دارد. از آن می‌توان برای باز کردن یک فایل و ارسال استریم آن به توابع WinRT و یا ثبت استریم WinRT در یک فایل استفاده کرد.


خواندن فایل‌های PDF و تبدیل صفحات آن‌ها به تصویر

در ادامه کد کامل استفاده از API جدید ویندوز 8.1 را جهت خواندن فایل‌های PDF ملاحظه می‌کنید. این امکانات جدید در فضای نام Windows.Data.Pdf قرار دارند و صرفا امکان خواندن فایل‌های PDF را تدارک دیده‌اند.
using System;
using System.IO;
using System.Threading.Tasks;
using Windows.Data.Pdf;
using Nito.AsyncEx;

namespace ConsoleWin81PdfApiTest
{
    class Program
    {
        static void Main(string[] args)
        {
            AsyncContext.Run(async () =>
            {
                await test();
            });
        }

        private static async Task test()
        {
            using (var randomAccessStream = File.Open("PieChartPdfReport.pdf", FileMode.Open).AsRandomAccessStream())
            {
                var pdfDocument = await PdfDocument.LoadFromStreamAsync(randomAccessStream);
                for (uint i = 0; i < pdfDocument.PageCount; i++)
                {
                    using (var page = pdfDocument.GetPage(i))
                    {
                        /*var renderOptions = new PdfPageRenderOptions
                        {
                            BackgroundColor = Colors.LightGray,
                            DestinationHeight = (uint) (page.Size.Height*10)
                        };*/

                        using (var stream = File.Open(string.Format("page-{0}.png", i + 1), FileMode.OpenOrCreate).AsRandomAccessStream())
                        {
                            await page.RenderToStreamAsync(stream/*, renderOptions*/);
                            await stream.FlushAsync();
                        }
                    }
                }
            }
        }
    }
}

توضیحات:
- متد AsyncContext.Run جزو امکانات Nito.AsyncEx است و امکان نوشتن کدهای await دار را در متد Main یک برنامه‌ی کنسول فراهم می‌کند.
- متد  File.Openدات نت، خروجی از نوع استریم دارد. برای تبدیل آن به نوع IRandomAccessStream، از متد الحاقی AsRandomAccessStream که پیشتر تهیه کردیم، می‌توان استفاده کرد.
- در ادامه متد PdfDocument.LoadFromStreamAsync این استریم خاص را دریافت کرده و امکان دسترسی به API ویندوز 8.1 را میسر می‌کند.
- توسط متد pdfDocument.GetPage می‌توان به صفحات مختلف فایل PDF باز شده دسترسی یافت. در اینجا متد page.RenderToStreamAsync، سبب رندر شدن صفحه با فرمت PNG می‌شود. این خروجی نهایتا باید در یک استریم از نوع IRandomAccessStream ثبت شود. در اینجا نیز می‌توان از متد File.Open در حالت FileMode.OpenOrCreate استفاده کرد.
- اگر می‌خواهید ابعاد تصویر نهایی و ویژگی‌های آن‌را تغییر دهید، می‌توان از پارامتر دوم متد page.RenderToStreamAsync استفاده کرد که شیءایی از نوع PdfPageRenderOptions را می‌پذیرد.


کدهای کامل این پروژه را از اینجا می‌توانید دریافت کنید
MicrosoftStreamExtensions.zip


برای مطالعه بیشتر
How to use specific WinRT API from Desktop apps
How to call WinRT APIs from .NET desktop apps
مطالب
Base64 و کاربرد جالب آن
Base64 یک مبنای عددی است که یکی از کاربرد‌های آن در نرم افزار‌ها انتقال اطلاعات فایل باینری است. به عنوان مثال با تبدیل محتوای باینری یک تصویر به مبنای 64 میتونید اون تصویر رو در دل فایل هایی نظیر HTML و CSS و SVG قرار بدید؛ یا به اصطلاح اون تصویر رو Embeded (توکار) کنید.

نکته: در Base64 برای هر 6 بیت یک کاراکتر معادل در مبنای 64 در نظر گرفته میشه، به همین دلیل در هنگام تبدیل برای جلوگیری از Lost (گم) شدن داده‌ها عملیات مورد نظر رو بر روی سه بایت داده انجام میدیم. که با این کار برای هر 3 بایت (24 بیت) 4 کاراکتر معادل خواهیم داشت؛ به این ترتیب حجم نهایی فایل تبدیل شده در واحد بایت، برابر است با:

(حجم نهایی) = (3 / حجم کل فایل) + (حجم کل فایل)


و حالا نحوه تبدیل فایل تصویر به Base64:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Text;
using System.Windows.Forms;

namespace Image2Base64Converter
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void btnOpen_Click(object sender, EventArgs e)
        {
            OpenFileDialog ofd = new OpenFileDialog();
            ofd.Multiselect = false;
            ofd.Filter = "Pictures |*.bmp;*.jpg;*.jpeg;*.png";

            if (ofd.ShowDialog(this) == DialogResult.OK)
            {
                txtImagePath.Text = ofd.FileName;
            }
        }

        private void btnConvert_Click(object sender, EventArgs e)
        {
            if (!File.Exists(txtImagePath.Text))
            {
                MessageBox.Show("File not found!");
                return;
            }

            btnCopy.Enabled = false;
            btnConvert.Enabled = false;
            txtOutput.Enabled = false;

            BackgroundWorker bworker = new BackgroundWorker();
            bworker.DoWork += (o, a) =>
            {
                string header = "data:image/{0};base64,";
                header = string.Format(header, txtImagePath.Text.GetExtension());

                StringBuilder data = new StringBuilder();

                byte[] buffer;
                BinaryReader head = new BinaryReader(
                    new FileStream(txtImagePath.Text, FileMode.Open));

                buffer = head.ReadBytes(6561);
                while (buffer.Length > 0)
                {
                    data.Append(Convert.ToBase64String(buffer));
                    buffer = head.ReadBytes(6561);
                }

                head.Close();
                head.Dispose();

                this.Invoke(new Action(() =>
                {
                    txtOutput.ResetText();
                    txtOutput.AppendText(header);
                    txtOutput.AppendText(data.ToString());

                    btnCopy.Enabled = true;
                    btnConvert.Enabled = true;
                    txtOutput.Enabled = true;
                }));
            };

            bworker.RunWorkerAsync();
        }

        private void btnCopy_Click(object sender, EventArgs e)
        {
            Clipboard.SetText(txtOutput.Text);
        }
    }

    public static class ExtensionMethods
    {
        public static string GetExtension(this string str)
        {
            string ext = string.Empty;
            for (int i = str.Length - 1; i >= 0; i--)
            {
                if (str[i] == '.')
                {
                    break;
                }

                ext = str[i] + ext;
            }

            return ext;
        }
    }
}
توضیح کد:
در خط 44 یک BackgroundWorker تعریف شده است تا عملیات تبدیل در یک ترد جداگانه انجام شود، که در عملیات‌های سنگین ترد اصلی برنامه آزاد باشد.
در خطوط 47 تا 64 کدهای مربوط به تبدیل قرار گرفته است:
در خط 47 و 48 ابتدا یک سرآیند برای معرفی داده‌های تبدیل شده ایجاد میکنیم؛ ",data:image/{0};base64" که در ان نوع فایل و پسوند آن را مشخص کرده و سپس الگوریتم به کار گرفته شده برای تبدیل آن را نیز ذکر میکنیم: ":data" + نوع فایل (image) + "/" + پسوند فایل + ";" + الگوریتم تبدیل + ",".
در انتهای کار این سرآیند تولید شده در ابتدای داده‌های تبدیل شده فایل، قرار خواهد گرفت.
در خط 50 یک StringBuilder برای نگهداری اطلاعات تبدیل شده ایجاد کرده ایم.
در خط 52 یک بافر برای خواندن اطلاعات باینری فایل ایجاد کرده ایم.
در خط 53 فایل مورد نظر را برای خواندن باز کرده ایم.
و در خطوط 56 تا 61 فایل مورد نظر را خوانده و پس از تبدیل در متغیر data ذخیره کرده ایم.
در نظر داشته باشید که برای عملکرد صحیح لازم است که عملیات تبدیل بر روی 3 بایت داده انجام شود؛ پس در هر بار  خواندن داده‌های فایل، باید مقدار داده ای که خوانده میشود ضریبی از 3 باشد.
در خطوط 63 و 64 نیز منابع اشغال شده سیستم را آزاد کرده ایم.
در انتها داده‌های مورد استفاده برای Embeded کردن تصویر برابر است با:
()header + data.ToString

embeded-images.htm : فایل نمونه برای نحوه استفاده از تصویر توکار.
Image2Base64-Converter.zip : سورس کامل برنامه ارائه شده.
مطالب
یافتن اکشن متد‌های Post ایی در ASP.NET MVC که فیلتر CSRF ندارند
روش مرسوم مقابله با حملات CSRF در ASP.NET MVC، استفاده از فیلتر امنیتی ValidateAntiForgeryToken بر روی اکشن متدهایی از نوع Post است و سپس فراخوانی Html.AntiForgeryToken در View متناظر.
با بالا رفتن تعداد اکشن متدهای یک پروژه، ممکن است استفاده از ValidateAntiForgeryToken فراموش شود. در ادامه مثالی را ملاحظه می‌کنید که یک پروژه‌ی ASP.NET MVC را جهت یافتن اکشن متدهای Post ایی که فیلتر ValidateAntiForgeryToken ندارند، اسکن می‌کند:

using System;
using System.Linq;
using System.Reflection;
// Add a ref. to \Program Files\Microsoft ASP.NET\ASP.NET MVC 4\Assemblies\System.Web.Mvc.dll
using System.Web.Mvc;
// Add a ref. to System.Web
using System.Web.UI;

namespace FindOutputCaches
{
    class Program
    {
        static void Main(string[] args)
        {
            var path = @"D:\path\bin\site.dll";
            var asmTarget = Assembly.LoadFrom(path);

            checkCsrfTokens(asmTarget);

            Console.WriteLine("Press a key...");
            Console.Read();
        }


        private static void checkCsrfTokens(Assembly asmTarget)
        {
            // یافتن کلیه کنترلرها
            var controllers = asmTarget.GetTypes()
                                       .Where(type => typeof(IController).IsAssignableFrom(type) &&
                                                     !type.Name.StartsWith("T4MVC"))
                                       .ToList();

            foreach (var controller in controllers)
            {
                // یافتن کلیه اکشن متدهای کنترلر جاری
                var actionMethods = controller.GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly)
                                              .Where(method => typeof(ActionResult).IsAssignableFrom(method.ReturnType))
                                              .ToList();

                foreach (var method in actionMethods)
                {                    
                    var httpPostAttributes = method.GetCustomAttributes(typeof(HttpPostAttribute), true);
                    if (httpPostAttributes == null || !httpPostAttributes.Any())
                        continue;

                    var csrfTokens = method.GetCustomAttributes(typeof(ValidateAntiForgeryTokenAttribute), true);
                    if (csrfTokens == null || !csrfTokens.Any())
                    {
                        Console.WriteLine("Detected [HttpPost] without [ValidateAntiForgeryToken] in:\n {0}-->{1}",
                                               controller.FullName, method.Name);
                    }
                }
            }
        }
    }
}
ابتدا مسیر اسمبلی کامپایل شده پروژه ASP.NET MVC که حاوی کنترلرهای برنامه است، باید مشخص گردد.
سپس در این اسمبلی، کلیه نوع‌های تعریف شده، یافت گردیده و آن‌هایی که پیاده سازی کننده IController هستند (یعنی کلاس‌های کنترلر واقعی برنامه)، جدا خواهند شد.
در ادامه در این کنترلرها، متدهایی را بررسی خواهیم کرد که دارای خروجی از نوع ActionResult باشند (فقط اکشن متدها مدنظر هستند). اگر این اکشن متد یافت شده دارای ویژگی HttpPost بود و همچنین ValidateAntiForgeryToken نداشت، یعنی یک مشکل امنیتی که باید برطرف شود.
مطالب
آشنایی با مفاهیم نوع داده Enum و توسعه آن - قسمت یکم
نوع داده شمارشی یا Enum، جهت تعاریف مقادیر ثابت و قابل شمارش در برنامه، بسیار کاربرد دارد. مقادیری که در این نوع داده تعریف می‌شوند بطور خودکار از عدد 0 شماره گذاری می‌شوند و به ترتیب یکی به آن‌ها اضافه می‌شود. برای مثال حالت زیر را در نظر بگیرید:
    public enum Grade
    {
        Failing,        // = 0
        BelowAverage,   // = 1
        Average,        // = 2
        VeryGood,       // = 3
        Excellent       // = 4
    }
  • در این حالت متد ()ToString نوع داده Enum عنوان مقادیر ثابت را بر می‌گرداند.
  • جهت برگشت مقدار عددی و شماره مقادیر ثابت‌های تعریف شده از متد ()ToString با فرمت D (شماره مقدار را بصورت Decimal نشان می‌دهد) و فرمت X جهت نمایش بصورت هگزا می‌توان استفاده کرد.
  • روش عرف برای نمایش مقدار عددی استفاده از تبدیل نوع صریح به int است.
به منظور درک بهتر موضوع، از یک برنامه کنسول استفاده می‌کنیم تا این نوع داده شمارشی را در آن استفاده کنیم.
static void Main(string[] args)
{
    Grade grade = Grade.Average;
    Console.WriteLine(grade.ToString());    // Print Avarage
    Console.WriteLine(grade.ToString("D")); // Print 2
    Console.WriteLine(grade.ToString("X")); // Print 00000002
    Console.WriteLine((int) grade);         //Print 2
    Console.ReadKey();
}
تغییر شماره (اندیس) مقادیر ثابت تعریف شده:
جهت تغییر شماره مقادیر کافیست بصورت زیر عمل کنیم:
public enum Grade
{
    Failing = 5,
    BelowAverage = 10,
    Average = BelowAverage + 5, // = 15
    VeryGood = 18,
    Excellent = 20
}
همانطور که در بالا می‌بینید برای مقدار Average بصورت ترکیبی عمل شده است.
بصورت پیش فرض کامپایلر سی شارپ از Int32 جهت نگهداری اعضای یک Enum استفاده می‌کند. هر چند غیر معقول به نظر می‌رسد اما شما می‌توانید این نوع را به byte - sbyte - short - ushort - uint - long تغییر دهید.
public enum Grade : byte
{
    Failing = 5,
    BelowAverage = 10,
    Average = BelowAverage + 5, // = 15
    VeryGood = 18,
    Excellent = 20
}
بدیهی است در این حالت خروجی دستور زیر 0F خواهد بود:
Console.WriteLine(grade.ToString("X")); // Print 0F
همچنین به خروجی دستورات زیر در حالت فوق توجه کنید:
Console.WriteLine("Underlying type: {0}", Enum.GetUnderlyingType(grade.GetType())); // Print System.Byte

Console.WriteLine("Type Code      : {0}", grade.GetTypeCode()); // Print Byte
و البته این:
Console.WriteLine("Value : {0}", (int)grade); // Print 15
در قسمت دوم این مطلب با استفاده از فضای نام System.Reflection و Extension Method‌ها و Custom Attribute کمی مقادیر Enum را توسعه خواهیم داد.
نظرات مطالب
بررسی مقدمات کتابخانه‌ی JSON.NET
یک نکته‌ی تکمیلی
استفاده از استریم‌ها برای کار با فایل‌ها در JSON.NET
        public static T DeserializeFromFile<T>(string filePath, JsonSerializerSettings settings = null)
        {
            if (!File.Exists(filePath))
                return default(T);

            using (var fileStream = File.OpenRead(filePath))
            {
                using (var streamReader = new StreamReader(fileStream))
                {
                    using (var reader = new JsonTextReader(streamReader))
                    {
                        var serializer = settings == null ? JsonSerializer.Create() : JsonSerializer.Create(settings);
                        return serializer.Deserialize<T>(reader);
                    }
                }
            }
        }

        public static void SerializeToFile(string filePath, object data, JsonSerializerSettings settings = null)
        {
            using (var fileStream = new FileStream(filePath, FileMode.Create))
            {
                using (var streamReader = new StreamWriter(fileStream))
                {
                    using (var reader = new JsonTextWriter(streamReader))
                    {
                        var serializer = settings == null ? JsonSerializer.Create() : JsonSerializer.Create(settings);
                        serializer.Serialize(reader, data);
                    }
                }
            }
        }
مطالب
هدایت خودکار کاربر به صفحه لاگین در حین اعمال Ajax ایی
در ASP.NET MVC به کمک فیلتر Authorize می‌توان کاربران را در صورت درخواست دسترسی به کنترلر و یا اکشن متد خاصی در صورت لزوم و عدم اعتبارسنجی کامل، به صفحه لاگین هدایت کرد. این مساله در حین postback کامل به سرور به صورت خودکار رخ داده و کاربر به Login Url ذکر شده در web.config هدایت می‌شود. اما در مورد اعمال Ajax ایی چطور؟ در این حالت خاص، فیلتر Authorize قابلیت هدایت خودکار کاربران را به صفحه لاگین، ندارد. در ادامه نحوه رفع این نقیصه را بررسی خواهیم کرد.

تهیه فیلتر سفارشی SiteAuthorize

برای بررسی اعمال Ajaxایی، نیاز است فیلتر پیش فرض Authorize سفارشی شود:
using System;
using System.Net;
using System.Web.Mvc;

namespace MvcApplication28.Helpers
{
    [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = true)]
    public sealed class SiteAuthorizeAttribute : AuthorizeAttribute
    {
        protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
        {
            if (filterContext.HttpContext.Request.IsAuthenticated)
            {
                throw new UnauthorizedAccessException(); //to avoid multiple redirects
            }
            else
            {
                handleAjaxRequest(filterContext);
                base.HandleUnauthorizedRequest(filterContext);
            }
        }

        private static void handleAjaxRequest(AuthorizationContext filterContext)
        {
            var ctx = filterContext.HttpContext;
            if (!ctx.Request.IsAjaxRequest())
                return;

            ctx.Response.StatusCode = (int)HttpStatusCode.Forbidden;
            ctx.Response.End();
        }
    }
}
در فیلتر فوق بررسی handleAjaxRequest اضافه شده است. در اینجا درخواست‌های اعتبار سنجی نشده از نوع Ajax ایی خاتمه داده شده و سپس StatusCode ممنوع (403) به کلاینت بازگشت داده می‌شود. در این حالت کلاینت تنها کافی است StatusCode یاده شده را مدیریت کند:
using System.Web.Mvc;
using MvcApplication28.Helpers;

namespace MvcApplication28.Controllers
{
    public class HomeController : Controller
    {
        public ActionResult Index()
        {
            return View();
        }

        [SiteAuthorize]
        [HttpPost]        
        public ActionResult SaveData(string data)
        {
            if(string.IsNullOrWhiteSpace(data))
                return Content("NOk!");

            return Content("Ok!");
        }
    }
}
در کد فوق نحوه استفاده از فیلتر جدید SiteAuthorize را ملاحظه می‌کنید. View ارسال کننده اطلاعات به اکشن متد SaveData، در ادامه بررسی می‌شود:
@{
    ViewBag.Title = "Index";
    var postUrl = this.Url.Action(actionName: "SaveData", controllerName: "Home");
}
<h2>
    Index</h2>
@using (Html.BeginForm(actionName: "SaveData", controllerName: "Home",
                method: FormMethod.Post, htmlAttributes: new { id = "form1" }))
{
    @Html.TextBox(name: "data")
    <br />
    <span id="btnSave">Save Data</span>
}
@section Scripts
{
    <script type="text/javascript">
        $(document).ready(function () {
            $("#btnSave").click(function (event) {
                $.ajax({
                    type: "POST",
                    url: "@postUrl",
                    data: $("#form1").serialize(),
                    // controller is returning a simple text, not json  
                    complete: function (xhr, status) {
                        var data = xhr.responseText;
                        if (xhr.status == 403) {
                            window.location = "/login";
                        }
                    }
                });
            });
        });
    </script>
}
تنها نکته جدید کدهای فوق، بررسی xhr.status == 403 است. اگر فیلتر SiteAuthorize کد وضعیت 403 را بازگشت دهد، به کمک مقدار دهی window.location، مرورگر را وادار خواهیم کرد تا صفحه کنترلر login را نمایش دهد. این کد جاوا اسکریپتی، با تمام مرورگرها سازگار است.


نکته تکمیلی:
در متد handleAjaxRequest، می‌توان یک JavaScriptResult را نیز بازگشت داد تا همان کدهای مرتبط با window.location را به صورت خودکار به صفحه تزریق کند:
filterContext.Result =  new JavaScriptResult { Script="window.location = '" + redirectToUrl + "'"};
البته این روش بسته به نحوه استفاده از jQuery Ajax ممکن است نتایج دلخواهی را حاصل نکند. برای مثال اگر قسمتی از صفحه جاری را پس از دریافت نتایج Ajax ایی از سرور، تغییر می‌دهید، صفحه لاگین در همین قسمت در بین کدهای صفحه درج خواهد شد. اما روش یاد شده در مثال فوق در تمام حالت‌ها کار می‌کند.
نظرات اشتراک‌ها
نکاتی که باید جهت بررسی یکسان بودن دو URL بررسی کرد
موارد مهم این نکات رو اگر تبدیل به یک متد کمکی کنیم، کلاس زیر بدست خواهد آمد:
using System;
using System.Web;

namespace UrlNormalizationTest
{
    public static class UrlNormalization
    {
        public static bool AreTheSameUrls(this string url1, string url2)
        {
            url1 = url1.NormalizeUrl();
            url2 = url2.NormalizeUrl();
            return url1.Equals(url2);
        }

        public static bool AreTheSameUrls(this Uri uri1, Uri uri2)
        {
            var url1 = uri1.NormalizeUrl();
            var url2 = uri2.NormalizeUrl();
            return url1.Equals(url2);
        }

        public static string[] DefaultDirectoryIndexes = new[]
            {
                "default.asp",
                "default.aspx",
                "index.htm",
                "index.html",
                "index.php"
            };

        public static string NormalizeUrl(this Uri uri)
        {
            var url = urlToLower(uri);
            url = limitProtocols(url);
            url = removeDefaultDirectoryIndexes(url);
            url = removeTheFragment(url);
            url = removeDuplicateSlashes(url);
            url = addWww(url);
            url = removeFeedburnerPart(url);
            return removeTrailingSlashAndEmptyQuery(url);
        }

        public static string NormalizeUrl(this string url)
        {
            return NormalizeUrl(new Uri(url));
        }

        private static string removeFeedburnerPart(string url)
        {
            var idx = url.IndexOf("utm_source=", StringComparison.Ordinal);
            return idx == -1 ? url : url.Substring(0, idx - 1);
        }

        private static string addWww(string url)
        {
            if (new Uri(url).Host.Split('.').Length == 2 && !url.Contains("://www."))
            {
                return url.Replace("://", "://www.");
            }
            return url;
        }

        private static string removeDuplicateSlashes(string url)
        {
            var path = new Uri(url).AbsolutePath;
            return path.Contains("//") ? url.Replace(path, path.Replace("//", "/")) : url;
        }

        private static string limitProtocols(string url)
        {
            return new Uri(url).Scheme == "https" ? url.Replace("https://", "http://") : url;
        }

        private static string removeTheFragment(string url)
        {
            var fragment = new Uri(url).Fragment;
            return string.IsNullOrWhiteSpace(fragment) ? url : url.Replace(fragment, string.Empty);
        }

        private static string urlToLower(Uri uri)
        {
            return HttpUtility.UrlDecode(uri.AbsoluteUri.ToLowerInvariant());
        }

        private static string removeTrailingSlashAndEmptyQuery(string url)
        {
            return url
                    .TrimEnd(new[] { '?' })
                    .TrimEnd(new[] { '/' });
        }

        private static string removeDefaultDirectoryIndexes(string url)
        {
            foreach (var index in DefaultDirectoryIndexes)
            {
                if (url.EndsWith(index))
                {
                    url = url.TrimEnd(index.ToCharArray());
                    break;
                }
            }
            return url;
        }
    }
}
با این تست‌ها جهت بررسی آن:
using NUnit.Framework;
using UrlNormalizationTest;

namespace UrlNormalization.Tests
{
    [TestFixture]
    public class UnitTests
    {
        [Test]
        public void Test1ConvertingTheSchemeAndHostToLowercase()
        {
            var url1 = "HTTP://www.Example.com/".NormalizeUrl();
            var url2 = "http://www.example.com/".NormalizeUrl();

            Assert.AreEqual(url1, url2);
        }

        [Test]
        public void Test2CapitalizingLettersInEscapeSequences()
        {
            var url1 = "http://www.example.com/a%c2%b1b".NormalizeUrl();
            var url2 = "http://www.example.com/a%C2%B1b".NormalizeUrl();

            Assert.AreEqual(url1, url2);
        }

        [Test]
        public void Test3DecodingPercentEncodedOctetsOfUnreservedCharacters()
        {
            var url1 = "http://www.example.com/%7Eusername/".NormalizeUrl();
            var url2 = "http://www.example.com/~username/".NormalizeUrl();

            Assert.AreEqual(url1, url2);
        }

        [Test]
        public void Test4RemovingTheDefaultPort()
        {
            var url1 = "http://www.example.com:80/bar.html".NormalizeUrl();
            var url2 = "http://www.example.com/bar.html".NormalizeUrl();

            Assert.AreEqual(url1, url2);
        }

        [Test]
        public void Test5AddingTrailing()
        {
            var url1 = "http://www.example.com/alice".NormalizeUrl();
            var url2 = "http://www.example.com/alice/?".NormalizeUrl();

            Assert.AreEqual(url1, url2);
        }

        [Test]
        public void Test6RemovingDotSegments()
        {
            var url1 = "http://www.example.com/../a/b/../c/./d.html".NormalizeUrl();
            var url2 = "http://www.example.com/a/c/d.html".NormalizeUrl();

            Assert.AreEqual(url1, url2);
        }

        [Test]
        public void Test7RemovingDirectoryIndex1()
        {
            var url1 = "http://www.example.com/default.asp".NormalizeUrl();
            var url2 = "http://www.example.com/".NormalizeUrl();

            Assert.AreEqual(url1, url2);
        }

        [Test]
        public void Test7RemovingDirectoryIndex2()
        {
            var url1 = "http://www.example.com/default.asp?id=1".NormalizeUrl();
            var url2 = "http://www.example.com/default.asp?id=1".NormalizeUrl();

            Assert.AreEqual(url1, url2);
        }

        [Test]
        public void Test7RemovingDirectoryIndex3()
        {
            var url1 = "http://www.example.com/a/index.html".NormalizeUrl();
            var url2 = "http://www.example.com/a/".NormalizeUrl();

            Assert.AreEqual(url1, url2);
        }

        [Test]
        public void Test8RemovingTheFragment()
        {
            var url1 = "http://www.example.com/bar.html#section1".NormalizeUrl();
            var url2 = "http://www.example.com/bar.html".NormalizeUrl();

            Assert.AreEqual(url1, url2);
        }

        [Test]
        public void Test9LimitingProtocols()
        {
            var url1 = "https://www.example.com/".NormalizeUrl();
            var url2 = "http://www.example.com/".NormalizeUrl();

            Assert.AreEqual(url1, url2);
        }

        [Test]
        public void Test10RemovingDuplicateSlashes()
        {
            var url1 = "http://www.example.com/foo//bar.html".NormalizeUrl();
            var url2 = "http://www.example.com/foo/bar.html".NormalizeUrl();

            Assert.AreEqual(url1, url2);
        }

        [Test]
        public void Test11AddWww()
        {
            var url1 = "http://example.com/".NormalizeUrl();
            var url2 = "http://www.example.com".NormalizeUrl();

            Assert.AreEqual(url1, url2);
        }

        [Test]
        public void Test12RemoveFeedburnerPart()
        {
            var url1 = "http://site.net/2013/02/firefox-19-released/?utm_source=rss&utm_medium=rss&utm_campaign=firefox-19-released".NormalizeUrl();
            var url2 = "http://site.net/2013/02/firefox-19-released".NormalizeUrl();

            Assert.AreEqual(url1, url2);
        }
    }
}
مطالب
WPF4 و ویندوز 7 : به خاطر سپاری لیست آخرین فایل‌های گشوده شده توسط برنامه

اگر به برنامه‌های جدید نوشته شده برای ویندوز 7 دقت کنیم، از یک سری امکانات مخصوص آن جهت بهبود دسترسی پذیری به قابلیت‌هایی که ارائه می‌دهند، استفاده شده است. برای مثال برنامه‌ی OneNote مجموعه‌ی آفیس را در نظر بگیرید. اگر بر روی آیکون آن در نوار وظیفه‌ی ویندوز کلیک راست کنیم، لیست آخرین فایل‌های گشوده شده توسط آن مشخص است و با کلیک بر روی هر کدام، به سادگی می‌توان این فایل را گشود. یک چنین قابلیتی در منوی آغازین ویندوز نیز تعبیه شده است (شکل‌های زیر):




خبر خوب اینکه برای اضافه کردن این قابلیت به برنامه‌های WPF4 نیازی به کد نویسی نیست و این موارد که تحت عنوان استفاده از Jump list ویندوز 7 تعریف شده‌اند، با کمی دستکاری فایل App.Xaml برنامه، فعال می‌گردند:

<Application x:Class="WpfApplication1.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
StartupUri="MainWindow.xaml">
<Application.Resources>
</Application.Resources>
<JumpList.JumpList>
<JumpList ShowRecentCategory="True" />
</JumpList.JumpList>
</Application>
همین! از این پس هر فایلی که توسط برنامه‌ی شما با استفاده از common file dialog boxes باز شود به صورت خودکار به لیست مذکور اضافه می‌گردد (بدیهی است Jump lists جزو ویژگی‌های ویندوز 7 است و در سایر سیستم عامل‌ها ندید گرفته خواهد شد).


سؤال: من اینکار را انجام دادم ولی کار نمی‌کنه!؟

پاسخ: بله. کار نمی‌کنه! این قابلیت تنها زمانی فعال خواهد شد که علاوه بر نکته‌ی فوق، پسوند فایل یا فایل‌هایی نیز به برنامه‌ی شما منتسب شده باشد. این انتساب‌ها مطلب جدیدی نیست و در تمام برنامه‌های ویندوزی باید توسط بکارگیری API ویندوز مدیریت شود. قطعه کد زیر اینکار را انجام خواهد داد:
using System;
using System.Runtime.InteropServices;
using Microsoft.Win32;

namespace Common.Files
{
//from : http://www.devx.com/vb2themax/Tip/19554?type=kbArticle&trk=MSCP
public class FileAssociation
{
const int ShcneAssocchanged = 0x8000000;
const int ShcnfIdlist = 0;

public static void CreateFileAssociation(
string extension,
string className,
string description,
string exeProgram)
{
// ensure that there is a leading dot
if (extension.Substring(0, 1) != ".")
extension = string.Format(".{0}", extension);

try
{
if (IsAssociated(extension)) return;

// create a value for this key that contains the classname
using (var key1 = Registry.ClassesRoot.CreateSubKey(extension))
{
if (key1 != null)
{
key1.SetValue("", className);
// create a new key for the Class name
using (var key2 = Registry.ClassesRoot.CreateSubKey(className))
{
if (key2 != null)
{
key2.SetValue("", description);
// associate the program to open the files with this extension
using (var key3 = Registry.ClassesRoot.CreateSubKey(string.Format(@"{0}\Shell\Open\Command", className)))
{
if (key3 != null) key3.SetValue("", string.Format(@"{0} ""%1""", exeProgram));
}
}
}
}
}

// notify Windows that file associations have changed
SHChangeNotify(ShcneAssocchanged, ShcnfIdlist, 0, 0);
}
catch (Exception ex)
{
//todo: log ...
}
}

// Return true if extension already associated in registry
public static bool IsAssociated(string extension)
{
return (Registry.ClassesRoot.OpenSubKey(extension, false) != null);
}

[DllImport("shell32.dll")]
public static extern void SHChangeNotify(int wEventId, int uFlags, int dwItem1, int dwItem2);
}
}
و مثالی از نحوه‌ی استفاده از آن:
private static void createFileAssociation()
{
var appPath = Assembly.GetExecutingAssembly().Location;
FileAssociation.CreateFileAssociation(".xyz", "xyz", "xyz File",
appPath
);
}
لازم به ذکر است که این کد در ویندوز 7 فقط با دسترسی مدیریتی قابل اجرا است (کلیک راست و اجرا به عنوان ادمین) و در سایر حالات با خطای Access is denied متوقف خواهد شد. به همین جهت بهتر است برنامه‌ی نصاب مورد استفاده این نوع انتسابات را مدیریت کند؛ زیرا اکثر آن‌ها با دسترسی مدیریتی است که مجوز نصب را به کاربر جاری خواهند داد. اگر از فناوری Click once استفاده می‌کنید به این مقاله و اگر برای مثال از NSIS کمک می‌گیرید به این مطلب مراجعه نمائید.


سؤال: من این کارها را هم انجام دادم. الان به چه صورت از آن‌ استفاده کنم؟

زمانیکه کاربری بر روی یکی از این فایل‌های ذکر شده در لیست آخرین فایل‌های گشوده شده توسط برنامه کلیک کند، آدرس این فایل به صورت یک آرگومان به برنامه ارسال خواهد شد. برای مدیریت آن در WPF باید به فایل App.Xaml.cs مراجعه کرده و چند سطر زیر را به آن افزود:
    public partial class App
{
public App()
{
this.Startup += appStartup;
}

void appStartup(object sender, StartupEventArgs e)
{
if (e.Args.Any())
{
this.Properties["StartupFileName"] = e.Args[0];
}
}
//...

در این کد، e.Args حاوی مسیر فایل انتخابی است. برای مثال در اینجا مقدار آن به خاصیت StartupFileName انتساب داده شده است. این خاصیت در برنامه‌های WPF به صورت یک خاصیت عمومی تعریف شده است و در سراسر برنامه (مثلا در رخداد آغاز فرم اصلی آن یا هر جای دیگری) به صورت زیر قابل دسترسی است:
var startupFileName = Application.Current.Properties["StartupFileName"];

سؤال: برنامه‌ی من از OpenFileDialog برای گشودن فایل‌ها استفاده نمی‌کند. آیا راه دیگری برای افزودن مسیرهای باز شده به Jump lists ویندوز 7 وجود دارد؟

پاسخ: بله. همانطور که می‌دانید عناصر XAML با اشیاء دات نت تناظر یک به یک دارند. به این معنا که JumpList تعریف شده در ابتدای این مطلب در فایل App.XAML ، دقیقا معادل کلاسی به همین نام در دات نت فریم ورک است (تعریف شده در فضای نام System.Windows.Shell) و با کد نویسی نیز قابل دسترسی و مدیریت است. برای مثال:
var jumpList = JumpList.GetJumpList(App.Current);
var jumpPath = new JumpPath();
jumpPath.Path = "some path goes here....";
// If the CustomCategory property is null
// or Empty, the item is added to the Tasks category
jumpPath.CustomCategory = "Files";
JumpList.AddToRecentCategory(jumpPath);
jumpList.Apply();
به همین ترتیب،‌ JumpPath ذکر شده در کدهای فوق، در کدهای XAML نیز قابل تعریف است:
<Application x:Class="Win7Wpf4.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
StartupUri="MainWindow.xaml">
<Application.Resources>
</Application.Resources>
<JumpList.JumpList>
<JumpList ShowRecentCategory="True">
<JumpPath
CustomCategory="Files"
Path="Some path goes here..."
/>
</JumpList>
</JumpList.JumpList>
</Application>

نظرات مطالب
استفاده از لوسین برای برجسته سازی عبارت جستجو شده در نتایج حاصل
چطور می‌شود چندین جدول مختلف رو ایندکس کرد؟ هربار ایندکس میکنم اطلاعات قبلی از بین میره
 private static FSDirectory _shopDirectory;

 private static FSDirectory ShopDirectory
        {
            get
            {
                if (_shopDirectory == null)
                    _shopDirectory = FSDirectory.Open(new DirectoryInfo(ShopPath));
                if (IndexWriter.IsLocked(_shopDirectory))
                    IndexWriter.Unlock(_shopDirectory);
                var lockFilePath = Path.Combine(ShopPath, "write.lock");
                if (File.Exists(lockFilePath))
                    File.Delete(lockFilePath);
                return _shopDirectory;
            }
        }

 public static void CreateProducSkustIndex(List<ProductSkuLuceneViewModel> productSkuLucenes)
        {
            var directory = ShopDirectory;
            var analyzer = new StandardAnalyzer(_version, PersianStopwords.List);
            using (var writer = new IndexWriter(directory, analyzer, create: true, mfl: IndexWriter.MaxFieldLength.UNLIMITED))
            {
                productSkuLucenes.ForEach(row => writer.AddDocument(MapProductToDocument(row)));

                writer.Optimize();
                writer.Commit();
                writer.Close();
            }
        }
 public static void CreateProducCategoriestIndex(List<CategoryLuceneViewModel> categoryLucenes)
        {
            var directory = ShopDirectory;
            var analyzer = new StandardAnalyzer(_version, PersianStopwords.List);
            using (var writer = new IndexWriter(directory, analyzer, create: true, mfl: IndexWriter.MaxFieldLength.UNLIMITED))
            {
                categoryLucenes.ForEach(row => writer.AddDocument(MapProductCategoryToDocument(row)));

                writer.Optimize();
                writer.Commit();
                writer.Close();
            }
        }

بازخوردهای پروژه‌ها
ایراد در نمایش جزییات نمایش مطالب و TreeView Extension
زمانی که روی جزییات نمایش  مطلبی کلیک میکنیم خطای Helper مربوط به  TreeView  رو میگیره بعد از بررسی متوجه شدم کد TreeView ربطی به پروژه نداره و اصلا داخل پوشه  Helper  تعریف نشده.

خطای دریافتی بعد از زدن روی دکمه مشاهده
( به ازای کامنت هر مطلب  از TreeView  استفاده شده، خطا میدهد)


HttpCompileException: d:\IrisCms-master\Iris.Web\Views\Comment\_PostComments.cshtml(7): error CS1061: 'System.Web.Mvc.HtmlHelper<System.Collections.Generic.IEnumerable<Iris.DomainClasses.Entities.Comment>>' does not contain a definition for 'TreeView' and no extension method 'TreeView' accepting a first argument of type 'System.Web.Mvc.HtmlHelper<System.Collections.Generic.IEnumerable<Iris.DomainClasses.Entities.Comment>>' could be found (are you missing a using directive or an assembly reference?) 


کد TreeView
using System.Collections.Generic;
using System.Web.Mvc;
using Iris.Servicelayer.Interfaces;

namespace Iris.Web.Controllers
{
    public partial class TreeViewController : Controller
    {
        private readonly IPageService _pageService;

        public TreeViewController(IPageService pageService)
        {
            _pageService = pageService;
        }

        public virtual ActionResult Index()
        {
            List<TreeViewLocation> locations = GetLocations();

            return View(_pageService.GetNavBarData(x => x.Body.Equals("dsad")));
        }

        public static List<TreeViewLocation> GetLocations()
        {
            var locations = new List<TreeViewLocation>
            {
                new TreeViewLocation
                {
                    Name = "United States",
                    ChildLocations =
                    {
                        new TreeViewLocation
                        {
                            Name = "Chicago",
                            ChildLocations =
                            {
                                new TreeViewLocation {Name = "Rack 1"},
                                new TreeViewLocation {Name = "Rack 2"},
                                new TreeViewLocation {Name = "Rack 3"},
                                new TreeViewLocation {Name = "Rack 3"},
                            }
                        },
                        new TreeViewLocation
                        {
                            Name = "Dallas",
                            ChildLocations =
                            {
                                new TreeViewLocation
                                {
                                    Name = "Rack 1",
                                    ChildLocations =
                                    {
                                        new TreeViewLocation
                                        {
                                            Name = "Rack 1",
                                            ChildLocations =
                                            {
                                                new TreeViewLocation {Name = "Rack 1"},
                                                new TreeViewLocation {Name = "Rack 2"},
                                                new TreeViewLocation {Name = "Rack 3"},
                                                new TreeViewLocation {Name = "Rack 3"},
                                            }
                                        },
                                        new TreeViewLocation {Name = "Rack 2"},
                                        new TreeViewLocation {Name = "Rack 3"},
                                        new TreeViewLocation {Name = "Rack 3"},
                                    }
                                },
                                new TreeViewLocation {Name = "Rack 2"},
                                new TreeViewLocation {Name = "Rack 3"},
                                new TreeViewLocation {Name = "Rack 3"},
                            }
                        },
                        new TreeViewLocation {Name = "Dallas"},
                        new TreeViewLocation {Name = "Dallas"},
                        new TreeViewLocation {Name = "Dallas"},
                        new TreeViewLocation {Name = "Dallas"},
                        new TreeViewLocation {Name = "Dallas"},
                        new TreeViewLocation {Name = "Dallas"},
                    }
                },
                new TreeViewLocation
                {
                    Name = "Canada",
                    ChildLocations =
                    {
                        new TreeViewLocation {Name = "Ontario"},
                        new TreeViewLocation {Name = "Windsor"}
                    }
                }
            };
            return locations;
        }
    }

    public class TreeViewLocation
    {
        public TreeViewLocation()
        {
            ChildLocations = new HashSet<TreeViewLocation>();
        }

        public int Id { get; set; }
        public string Name { get; set; }
        public ICollection<TreeViewLocation> ChildLocations { get; set; }
    }
}