نظرات مطالب
امن سازی برنامه‌های ASP.NET Core توسط IdentityServer 4x - قسمت ششم - کار با User Claims

تنظیمات کانفیگ :

public static class ConfigTest
{

    public static IEnumerable<IdentityResource> Resources => new List<IdentityResource>
    {
        new IdentityResources.OpenId(),
        new IdentityResources.Profile(),
        new IdentityResources.Address(),
        new IdentityResource
        {
            Name = "role",
            UserClaims = new List<string> {"role"}
        }
    };


    public static IEnumerable<ApiResource> ApiResources => new[]
        {
            new ApiResource
            {
                Name = "orderingapi",
                DisplayName = "Ordering Api",
                Description = "Allow the application to access Ordering Api on your behalf",
                Scopes = new List<string> { "api.ordering.read", "api.ordering.manager"},
                ApiSecrets = new List<Secret> {new Secret("OrderingScopeSecret".Sha256())}, // change me!
                UserClaims = new List<string> {"role"}
            }
        };


    public static IEnumerable<ApiScope> ApiScopes => new[]
        {
            new ApiScope("api.ordering.read", "Read Access to Ordering Api"),
            new ApiScope("api.ordering.manager",displayName:"manage Crud operation access to ordering api")
        };



    public static IEnumerable<Client> Clients => new List<Client>
    {
        new Client
        {
            ClientId = "oidcClient",
            ClientName = "Example Client Application",
            ClientSecrets = new List<Secret> {new Secret("VQGBtSDEK7tzIzSJyfCYqdHDTQHt7kD2VQ1hHWnY7Dw=".Sha256())}, // change me!
    
            AllowedGrantTypes = GrantTypes.Hybrid,
            RedirectUris = new List<string> {"https://localhost:6001/signin-oidc"},
            PostLogoutRedirectUris = new List<string>
            {
                "https://localhost:6001/signout-callback-oidc"
            },
            AllowedScopes = new List<string>
            {
                IdentityServerConstants.StandardScopes.OpenId,
                IdentityServerConstants.StandardScopes.Profile,
                IdentityServerConstants.StandardScopes.Address,
                "role",
                "api.ordering.manager"
            },

            RequirePkce=false,
            AllowOfflineAccess = true
        }
    };


    public static List<TestUser> Users => new List<TestUser>
    {
        new TestUser
        {
            SubjectId = "1",
            Username = "meysam",
            Password = "123",
            Claims = new List<Claim>
            {
                new Claim(JwtClaimTypes.Name,"meysanm"),
                new Claim(JwtClaimTypes.GivenName,"soleymani"),
                new Claim(JwtClaimTypes.Email, "meysam@test.com"),
                new Claim(JwtClaimTypes.Address,"amol city"),
                new Claim(JwtClaimTypes.Role, "admin")
            }
        },
        new TestUser
        {
            SubjectId = "2",
            Username = "ali",
            Password = "123",
            Claims = new List<Claim>
            {
                new Claim(JwtClaimTypes.Name,"ali"),
                new Claim(JwtClaimTypes.GivenName,"abbasi"),
                new Claim(JwtClaimTypes.Address,"tehran city"),
                new Claim(JwtClaimTypes.Role,"public")
            }
        }
    };
}


نظرات مطالب
نحوه‌ی استخراج شماره سریال سخت افزار برای تولید یک قفل سخت افزاری
می توان از این کلاس هم که در واقع بسط داده شده‌ی روش مذکور است استفاده نمود:
    public class FingerPrint
    {
        private static string fingerPrint = string.Empty;
        public static string Value()
        {
            if (string.IsNullOrEmpty(fingerPrint))
            {
                fingerPrint = GetHash("CPU >> " + cpuId() + "\nBIOS >> " +
            biosId() + "\nBASE >> " + baseId()
                    +"\nDISK >> "+ diskId() + "\nVIDEO >> " + 
                    videoId() +"\nMAC >> "+ macId());
            }
            return fingerPrint;
        }
        private static string GetHash(string s)
        {
            MD5 sec = new MD5CryptoServiceProvider();
            ASCIIEncoding enc = new ASCIIEncoding();
            byte[] bt = enc.GetBytes(s);
            return GetHexString(sec.ComputeHash(bt));
        }
        private static string GetHexString(byte[] bt)
        {
            string s = string.Empty;
            for (int i = 0; i < bt.Length; i++)
            {
                byte b = bt[i];
                int n, n1, n2;
                n = (int)b;
                n1 = n & 15;
                n2 = (n >> 4) & 15;
                if (n2 > 9)
                    s += ((char)(n2 - 10 + (int)'A')).ToString();
                else
                    s += n2.ToString();
                if (n1 > 9)
                    s += ((char)(n1 - 10 + (int)'A')).ToString();
                else
                    s += n1.ToString();
                if ((i + 1) != bt.Length && (i + 1) % 2 == 0) s += "-";
            }
            return s;
        }


        #region Original Device ID Getting Code
        //Return a hardware identifier
        private static string identifier
        (string wmiClass, string wmiProperty, string wmiMustBeTrue)
        {
            string result = "";
            System.Management.ManagementClass mc =
        new System.Management.ManagementClass(wmiClass);
            System.Management.ManagementObjectCollection moc = mc.GetInstances();
            foreach (System.Management.ManagementObject mo in moc)
            {
                if (mo[wmiMustBeTrue].ToString() == "True")
                {
                    //Only get the first one
                    if (result == "")
                    {
                        try
                        {
                            result = mo[wmiProperty].ToString();
                            break;
                        }
                        catch
                        {
                        }
                    }
                }
            }
            return result;
        }
        //Return a hardware identifier
        private static string identifier(string wmiClass, string wmiProperty)
        {
            string result = "";
            System.Management.ManagementClass mc =
        new System.Management.ManagementClass(wmiClass);
            System.Management.ManagementObjectCollection moc = mc.GetInstances();
            foreach (System.Management.ManagementObject mo in moc)
            {
                //Only get the first one
                if (result == "")
                {
                    try
                    {
                        result = mo[wmiProperty].ToString();
                        break;
                    }
                    catch
                    {
                    }
                }
            }
            return result;
        }
        private static string cpuId()
        {
            //Uses first CPU identifier available in order of preference
            //Don't get all identifiers, as it is very time consuming
            string retVal = identifier("Win32_Processor", "UniqueId");
            if (retVal == "") //If no UniqueID, use ProcessorID
            {
                retVal = identifier("Win32_Processor", "ProcessorId");
                if (retVal == "") //If no ProcessorId, use Name
                {
                    retVal = identifier("Win32_Processor", "Name");
                    if (retVal == "") //If no Name, use Manufacturer
                    {
                        retVal = identifier("Win32_Processor", "Manufacturer");
                    }
                    //Add clock speed for extra security
                    retVal += identifier("Win32_Processor", "MaxClockSpeed");
                }
            }
            return retVal;
        }
        //BIOS Identifier
        private static string biosId()
        {
            return identifier("Win32_BIOS", "Manufacturer")
                + identifier("Win32_BIOS", "SMBIOSBIOSVersion")
            + identifier("Win32_BIOS", "IdentificationCode")
                + identifier("Win32_BIOS", "SerialNumber")
                + identifier("Win32_BIOS", "ReleaseDate")
                + identifier("Win32_BIOS", "Version");
        }
        //Main physical hard drive ID
        private static string diskId()
        {
            return identifier("Win32_DiskDrive", "Model")
            + identifier("Win32_DiskDrive", "Manufacturer")
            + identifier("Win32_DiskDrive", "Signature")
            + identifier("Win32_DiskDrive", "TotalHeads");
        }
        //Motherboard ID
        private static string baseId()
        {
            return identifier("Win32_BaseBoard", "Model")
            + identifier("Win32_BaseBoard", "Manufacturer")
            + identifier("Win32_BaseBoard", "Name")
            + identifier("Win32_BaseBoard", "SerialNumber");
        }
        //Primary video controller ID
        private static string videoId()
        {
            return identifier("Win32_VideoController", "DriverVersion")
            + identifier("Win32_VideoController", "Name");
        }
        //First enabled network card ID
        private static string macId()
        {
            return identifier("Win32_NetworkAdapterConfiguration",
                "MACAddress", "IPEnabled");
        }
        #endregion
    }
خروجی این کلاس Hexal است و به شکل زیر قابل استفاده می‌باشد:
/// Generates a 16 byte Unique Identification code of a computer 
/// Example: 4876-8DB5-EE85-69D3-FE52-8CF7-395D-2EA9

var computerId = FingerPrint.Value();

مطالب
پیدا کردن آیتم‌های تکراری در یک لیست به کمک LINQ

گاهی از اوقات نیاز می‌شود تا در یک لیست، آیتم‌های تکراری موجود را مشخص کرد. به صورت پیش فرض متد Distinct برای حذف مقادیر تکراری در یک لیست با استفاده از LINQ موجود است که البته آن‌هم اما و اگرهایی دارد که در ادامه به آن پرداخته خواهد شد، اما باز هم این مورد پاسخ سؤال اصلی نیست (نمی‌خواهیم موارد تکراری را حذف کنیم).

برای حذف آیتم‌های تکراری از یک لیست جنریک می‌توان متد زیر را نوشت:
public static List<T> RemoveDuplicates<T>(List<T> items)
{
return (from s in items select s).Distinct().ToList();
}
برای مثال:
public static void TestRemoveDuplicates()
{
List<string> sampleList =
new List<string>() { "A1", "A2", "A3", "A1", "A2", "A3" };
sampleList = RemoveDuplicates(sampleList);
foreach (var item in sampleList)
Console.WriteLine(item);
}
این متد بر روی لیست‌هایی با نوع‌های اولیه مانند string‌ و int و امثال آن درست کار می‌کند. اما اکنون مثال زیر را در نظر بگیرید:
public class Employee
{
public int ID { get; set; }
public string FName { get; set; }
public int Age { get; set; }
}

public static void TestRemoveDuplicates()
{
List<Employee> lstEmp = new List<Employee>()
{
new Employee(){ ID=1, Age=20, FName="F1"},
new Employee(){ ID=2, Age=21, FName="F2"},
new Employee(){ ID=1, Age=20, FName="F1"},
};

lstEmp = RemoveDuplicates<Employee>(lstEmp);

foreach (var item in lstEmp)
Console.WriteLine(item.FName);
}
اگر متد TestRemoveDuplicates را اجرا نمائید، رکورد تکراری این لیست جنریک حذف نخواهد شد؛ زیرا متد distinct بکارگرفته شده نمی‌داند اشیایی از نوع کلاس سفارشی Employee را چگونه باید با هم مقایسه نماید تا بتواند موارد تکراری آن‌ها را حذف کند.
برای رفع این مشکل باید از آرگومان دوم متد distinct جهت معرفی وهله‌ای از کلاسی که اینترفیس IEqualityComparer را پیاده سازی می‌کند، کمک گرفت.
public static IEnumerable<TSource> Distinct<TSource>(this IEnumerable<TSource> source, IEqualityComparer<TSource> comparer);
که نمونه‌ای از پیاده سازی آن به شرح زیر می‌تواند باشد:

public class EmployeeComparer : IEqualityComparer<Employee>
{
public bool Equals(Employee x, Employee y)
{
//آیا دقیقا یک وهله هستند؟
if (Object.ReferenceEquals(x, y)) return true;

//آیا یکی از وهله‌ها نال است؟
if (Object.ReferenceEquals(x, null) ||
Object.ReferenceEquals(y, null))
return false;

return x.Age == y.Age && x.FName == y.FName && x.ID == y.ID;
}

public int GetHashCode(Employee obj)
{
if (Object.ReferenceEquals(obj, null)) return 0;
int hashTextual = obj.FName == null ? 0 : obj.FName.GetHashCode();
int hashDigital = obj.Age.GetHashCode();
return hashTextual ^ hashDigital;
}
}
اکنون اگر یک overload برای متد RemoveDuplicates با درنظر گرفتن IEqualityComparerتهیه کنیم، به شکل زیر خواهد بود:
public static List<T> RemoveDuplicates<T>(List<T> items, IEqualityComparer<T> comparer)
{
return (from s in items select s).Distinct(comparer).ToList();
}
به این صورت متد آزمایشی ما به شکل زیر (که وهله‌ای از کلاس EmployeeComparer‌ به آن ارسال شده) تغییر خواهد کرد:
public static void TestRemoveDuplicates()
{
List<Employee> lstEmp = new List<Employee>()
{
new Employee(){ ID=1, Age=20, FName="F1"},
new Employee(){ ID=2, Age=21, FName="F2"},
new Employee(){ ID=1, Age=20, FName="F1"},
};

lstEmp = RemoveDuplicates(lstEmp, new EmployeeComparer());

foreach (var item in lstEmp)
Console.WriteLine(item.FName);
}
پس از این تغییر، حاصل این متد تنها دو رکورد غیرتکراری می‌باشد.

سؤال: برای یافتن آیتم‌های تکراری یک لیست چه باید کرد؟
احتمالا مقاله "روش‌هایی برای حذف رکوردهای تکراری" را به خاطر دارید. اینجا هم می‌توان کوئری LINQ ایی را نوشت که رکوردها را بر اساس سن، گروه بندی کرده و سپس گروه‌هایی را که بیش از یک رکورد دارند، انتخاب نماید.
public static void FindDuplicates()
{
List<Employee> lstEmp = new List<Employee>()
{
new Employee(){ ID=1, Age=20, FName="F1"},
new Employee(){ ID=2, Age=21, FName="F2"},
new Employee(){ ID=1, Age=20, FName="F1"},
};

var query = from c in lstEmp
group c by c.Age into g
where g.Count() > 1
select new { Age = g.Key, Count = g.Count() };

foreach (var item in query)
{
Console.WriteLine("Age {0} has {1} records", item.Age, item.Count);
}
}


Vote on iDevCenter
مطالب
امکان انجام محاسبات سمت کلاینت در EF Core
در دنیای NET. همواره دو نوع LINQ وجود داشته داشته است: LINQ to Objects و ... مابقی.  در حالت اول با <IEnumerable<T‌ها کار می‌کنیم که تمام عملیات در حافظه انجام می‌شود و در مابقی حالات یک <IQueryable<T وجود دارد که عبارت حاصل از آن جهت کاربردهای مختلفی به زبان‌های متفاوتی مانند SQL ترجمه می‌شوند. در هر دو حالت کلی، Syntax نهایی یکی است و تنها اگر به منبع داده‌ی آن‌ها دقت کنیم، می‌توانیم نوع آن‌ها را تشخیص دهیم. برای نمونه کوئری ذیل بر اساس منبع Blogs است که می‌تواند LINQ to Objects باشد و یا حالت <Queryable<Blog که قرار است به زبانی مشخص ترجمه شود:
var blogs = from blog in Blogs
   where blog.Name.Contains("Development")
   select blog;
اکنون فرض کنید که این عبارت قرار است به SQL ترجمه شده و سپس بر روی یک بانک اطلاعاتی اجرا شود. در این حالت مفسر LINQ باید بداند که متد Contains را چگونه به معادل SQL آن ترجمه کند و این ترجمه می‌تواند بر اساس بانک‌های اطلاعاتی مختلف، متفاوت نیز باشد. اما در حالت LINQ to Objects یک چنین مشکلی وجود ندارد و این ترجمه مستقیما بر روی متد Contains کلاس string انجام می‌شود.
اما اکنون چطور؟
var blogs = from blog in Blogs
   where blog.Name.ComputeHash() == 0
   select blog;
فرض کنید یک متد الحاقی را به نام ComputeHash به کلاس string اضافه کرده‌ایم. یک چنین کوئری را اگر بر روی EF 6.x اجرا کنیم، برنامه با یک استثناء متوقف خواهد شد؛ چون امکان ترجمه‌ی متد ComputeHash را به معادل SQL آن ندارد؛ اما EF Core برای انجام یک چنین کوئری‌هایی بهبود یافته‌است که به آن، محاسبات سمت کلاینت گفته می‌شود.


یک مثال: بررسی تاثیر ارزیابی‌های سمت کلاینت در EF Core

فرض کنید ساختار جدول بلاگ‌ها به صورت زیر است:
public class Blog
{
   public int BlogId { get; set; }
   public string Url { get; set; }  
}
همچنین یک متد الحاقی را به نام ComputeHash به صورت ذیل تعریف کرده‌ایم:
    public static class StringExtensions
    {
        public static int ComputeHash(this string str)
        {
            var hash = 0;
            foreach (var ch in str)
            {
                hash += (int)ch;
            }
            return hash;
        }
    }
اکنون می‌خواهیم بلاگ‌هایی را پیدا کنیم که Hash مربوط به Url آن‌ها بیشتر از 10 است (صرفا جهت نمایش این قابلیت جدید):
using (var context = new BloggingContext())
{
   var blogs = context.Blogs
     .Where(blog => blog.Url.ComputeHash() >= 10)
     .ToList();
   Console.WriteLine(blogs.First().Url);
}
اگر این کوئری را اجرا کنیم، یک چنین خروجی SQL ایی تولید خواهد شد و همچنین برنامه کرش هم نمی‌کند:
SELECT [blog].[BlogId], [blog].[Url]
   FROM [Blogs] AS [blog]
به این معنا که در ارزیابی‌های سمت کلاینت:
الف) مفسر LINQ در EF Core، شروع به ارزیابی کوئری نوشته شده می‌کند و هرجائیکه متدی را یافت و از درک آن عاجز بود (معادل SQL ایی را برای آن نیافت)، آن‌را از کوئری حذف می‌کند.
ب) کوئری SQL نهایی بدون متد ComputeHash بر روی بانک اطلاعاتی اجرا شده و نتیجه به سمت کلاینت بازگشت داده می‌شود. به همین جهت است که در خروجی SQL فوق خبری از متد ComputeHash نیست.
ج) اکنون که EF Core اطلاعات لازم را از سمت سرور دریافت کرده‌است، متد ComputeHash را در سمت کلاینت بر روی این نتیجه‌ی دریافتی اعمال می‌کند. یعنی مرحله‌ی آخر همان LINQ to Objects متداول خواهد بود.
به این ترتیب است که EF Core قابلیت اجرای هر نوع متدی را که معادل SQL ایی برای آن وجود ندارد، خواهد یافت.


چگونه متوجه شویم که ارزیابی سمت کلاینت رخ داده‌است؟

EF Core این قابلیت را دارد تا گزارش کاملی را از ارزیابی‌های سمت کلاینت صورت گرفته ارائه دهد. هرچند در مثال فوق متد الحاقی ComputeHash بسیار واضح است، اما برای نمونه متد string.Join نیز معادل SQL ایی ندارد:
var idUrls = context.Blogs
   .Select(b => new
   {
      IdUrlString = string.Join(", ", b.BlogId, b.Url),
   }).ToList();
این مثال بدون مشکل توسط EF Core و قابلیت جدید ارزیابی سمت کلاینت آن اجرا می‌شود، اما بهتر است از وقوع یک چنین رخ‌دادهایی مطلع شویم:
    public class BloggingContext : DbContext
    {
        public BloggingContext()
        { }

        public BloggingContext(DbContextOptions options)
            : base(options)
        { }

        public DbSet<Blog> Blogs { get; set; }

        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            if (!optionsBuilder.IsConfigured)
            {
                optionsBuilder.UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database=Demo.ClientSideEvaluation;Trusted_Connection=True;");
                optionsBuilder.ConfigureWarnings(warnings =>
                {
                    warnings.Log(CoreEventId.IncludeIgnoredWarning);
                    warnings.Log(RelationalEventId.QueryClientEvaluationWarning);
                });
            }
        }
    }
برای این منظور تنها کافی است درخواست فعالسازی لاگ کردن QueryClientEvaluationWarning را در قسمت ConfigureWarnings آن ارائه دهیم. در این حالت اگر برنامه را مجددا اجرا کنیم، ابتدا یک چنین خروجی لاگ می‌شود:
 warn: Microsoft.EntityFrameworkCore.Query[200500]
The LINQ expression 'where ([blog].Url.ComputeHash() >= 10)' could not be translated and will be evaluated locally.
عنوان کرده‌است که قابلیت ترجمه‌ی ComputeHash را به SQL نداشته و آن‌را در نهایت به صورت محلی و در سمت کلاینت محاسبه می‌کند.

اگر می‌خواهید ارزیابی سمت کلاینت را ممنوع کنید، در تنظیمات فوق warnings.Log را به warnings.Throw تغییر دهید. این مورد سبب خواهد شد تا اگر برنامه به این نوع ارزیابی‌ها رسید، با یک استثناء متوقف شود (شبیه به حالت EF 6.x).


تاثیر ارزیابی‌های سمت کلاینت بر روی کارآیی برنامه

هرچند قابلیت ارزیابی‌های سمت کلاینت بسیار مفید است اما باید دقت داشت:
الف) در این حالت چون ابتدا متدهایی که قابلیت ارزیابی در سمت سرور را دارا نیستند، حذف خواهند شد، ممکن است تمام رکوردها به سمت کلاینت بازگشت داده شده و سپس فیلترینگ نهایی در سمت کلاینت صورت گیرد. مانند مثال محاسبه‌ی hash که در SQL تولیدی آن، خبری از قسمت where نیست و این شرط در انتهای کار، در سمت کلاینت و به صورت LINQ to Objects اعمال می‌شود.
ب) این قابلیت ممکن است برنامه نویس‌ها را از تفکر در مورد یافتن روش‌های محاسباتی سمت سرور دور کند. برای مثال هر چند مثال string.Join نوشته شده در سمت کلاینت محاسبه خواهد شد و این کوئری بدون مشکل اجرا می‌شود، اما اگر آن‌را به صورت ذیل جایگزین کنیم:
var idUrls2 = context.Blogs
   .Select(b => new
   {
     IdUrlString = b.BlogId + "," + b.Url
   }).ToList();
اینبار به یک خروجی SQL قابل محاسبه‌ی در سمت سرور، خواهیم رسید:
SELECT (CAST([b].[BlogId] AS nvarchar(max)) + N',') + [b].[Url] AS [IdUrlString]
FROM [Blogs] AS [b]
به همین جهت حداقل لاگ کردن ارزیابی‌های سمت کلاینت را روشن کنید تا از وقوع یک چنین مسایلی مطلع گردید.


کدهای کامل این مثال را از اینجا می‌توانید دریافت کنید: ClientSideEvaluation.zip
مطالب
توسعه سیستم مدیریت محتوای DNTCms - قسمت سوم

در این قسمت به پیاده سازی و توضیح مدل‌های انجمن خواهیم پرداخت. قبل از شروع پیشنهاد می‌کنم مقالات قبلی را مطالعه کنید.
همکاران این قسمت:
سلمان معروفی 
سید مجتبی حسینی 
پیشنیاز این قسمت:
مقالات SQL Antipattern 

سعی کردیم چندین پروژه‌ی سورس باز را هم بررسی کنیم و در نهایت کاملترین و بهترین روش را پیاده سازی کنیم. NForum ، MyBB ، MVCForum ، بخش CMS مربوط به SmartStore و ساختار دیتابیس StackOverFlow ازجمله‌ی آنها هستند.

ساختار انجمن‌ها اغلب به شکل سلسله مراتبی می‌باشد و این مورد در دسته بندی آنها خیلی مفید خواهد بود. صرف اینکه بتوان برای این مورد یک مدل خود ارجاع در نظر گرفت کاری خاصی ندارد. ولی مشکل از آنجا شروع می‌شود که بخواهیم برای انجمن هایمان مدیرانی هم تعیین کنیم یا فقط تا عمق مشخصی را واکشی کنیم و خیلی چالش برانگیزتر از اینها، اگر لازم باشد دسترسی‌های مدیران یک انجمن قابلیت اعمال بر زیرشاخه‌ها را داشته باشد و در مقابل زیرشاخه‌ها هم بتوانند از این ارث بری ممانعت کنند و از این نوع چالش‌های شیرین دیگر.
مدل انجمن
  /// <summary>
    /// Represents the Forum
    /// </summary>
    public class Forum
    {

        #region Properties
        /// <summary>
        /// gets or sets Id that Identify Forum
        /// </summary>
        public virtual long Id { get; set; }
        /// <summary>
        /// gets or sets Forum's title
        /// </summary>
        public virtual string Title { get; set; }
        /// <summary>
        /// gets or sets Description of forum
        /// </summary>
        public virtual string Description { get; set; }
        /// <summary>
        /// gets or sets value indicating Custom Slug
        /// </summary>
        public virtual string SlugUrl { get; set; }
        /// <summary>
        /// gets or sets order for display forum
        /// </summary>
        public virtual long DisplayOrder { get; set; }
        /// <summary>
        /// Indicating This Forum is Active or Not
        /// </summary>
        public virtual bool IsActive { get; set; }
        /// <summary>
        /// Indicating This Forum is Close or Not
        /// </summary>
        public virtual bool IsClose { get; set; }
        /// <summary>
        /// Indicating This Forum is Private or Not
        /// </summary>
        public virtual bool IsPrivate { get; set; }
        /// <summary>
        /// sets or gets password for login to Private forums
        /// </summary>
        public virtual string PasswordHash { get; set; }
        /// <summary>
        /// sets or gets depth of forum in tree structure of forums
        /// </summary>
        public virtual int Depth { get; set; }
        /// <summary>
        /// sets or gets Count of  posts That they are Approved
        /// </summary>
        public virtual long ApprovedPostsCount { get; set; }
        /// <summary>
        /// sets or gets Count of  topics That they are Approved
        /// </summary>
        public virtual long ApprovedTopicsCount { get; set; }
        /// <summary>
        /// Gets or sets the id of last topic
        /// </summary>
        public virtual long LastTopicId { get; set; }
        /// <summary>
        /// gets or sets date of creation of last topic
        /// </summary>
        public virtual DateTime? LastTopicCreatedOn { get; set; }
        /// <summary>
        /// gets or sets title of last topic
        /// </summary>
        public virtual string LastTopicTitle { get; set; }
        /// <summary>
        /// gets or sets creator of last topic
        /// </summary>
        public virtual string LastTopicCreator { get; set; }
        /// <summary>
        /// gets or sets id of creator that create last topic
        /// </summary>
        public virtual long LastTopicCreatorId { get; set; }
        /// <summary>
        /// Indicate in this Forum Moderate Topics Before Display 
        /// </summary>
        public virtual bool ModerateTopics { get; set; }
        /// <summary>
        /// Indicate in this Forum Moderate Posts Before Dipslay
        /// </summary>
        public virtual bool ModeratePosts { get; set; }
        /// <summary>
        /// gets or sets Count of posts that they are UnApproved
        /// </summary>
        public virtual long UnApprovedPostsCount { get; set; }
        /// <summary>
        /// gets or sets Count of topics that they are UnApproved
        /// </summary>
        public virtual long UnApprovedTopicsCount { get; set; }
        /// <summary>
        /// gets or sets Rowversion
        /// </summary>
        public virtual byte[] RowVersion { get; set; }
        /// <summary>
        /// gets or sets icon name with size 200*200 px for snippet 
        /// </summary>
        public virtual string SocialSnippetIconName { get; set; }
        /// <summary>
        /// gets or sets title for snippet
        /// </summary>
        public virtual string SocialSnippetTitle { get; set; }
        /// <summary>
        /// gets or sets description for snippet
        /// </summary>
        public virtual string SocialSnippetDescription { get; set; }
        /// <summary>
        /// gets or sets path for tree structure antipattern (1/3/4/23)
        /// </summary>
        public virtual string Path { get; set; }
        /// <summary>
        /// Indicate this forum inherit moderators from parent forum
        /// </summary>
        public virtual bool IsModeratorsInherited { get; set; }
        /// <summary>
        /// gets or set datetime that Last Post is Created In this forum. used for ForumTracking 
        /// </summary>
        public virtual DateTime? LastPostCreatedOn { get; set; }
        #endregion

        #region NavigationProperties
        /// <summary>
        /// sets or gets identifier forum's parent
        /// </summary>
        public virtual long? ParentId { get; set; }
        /// <summary>
        /// sets or gets forum's parent
        /// </summary>
        public virtual Forum Parent { get; set; }
        /// <summary>
        /// sets or gets sub forums of forum
        /// </summary>
        public virtual ICollection<Forum> Children { get; set; }
        /// <summary>
        /// set or get topics of forum
        /// </summary>
        public virtual ICollection<ForumTopic> Topics { get; set; }
        /// <summary>
        /// get or set moderators of this forum
        /// </summary>
        public virtual ICollection<ForumModerator> Moderators { get; set; }
        /// <summary>
        /// get or set Subscriptions List 
        /// </summary>
        public virtual ICollection<User> Subscribers { get; set; }
        /// <summary>
        /// get or set Announcements Collection of this Forum
        /// </summary>
        public virtual ICollection<ForumAnnouncement> Announcements { get; set; }
        /// <summary>
        /// get or set Trackers List Of this Forum
        /// </summary>
        public virtual ICollection<ForumTracker> Trackers  { get; set; }
        /// <summary>
        /// get or set Posts List that Posted in this forum for increase Performance for get Posts Count 
        /// </summary>
        public virtual ICollection<ForumPost> Posts  { get; set; }
        /// <summary>
        /// get or set 
        /// </summary>
        public virtual ICollection<ForumTopicTracker> TopicTrackers  { get; set; }
        #endregion
مدل بالا نشان دهنده‌ی ساختار انجمن‌های ما می‌باشد. خصوصیت هایی که نیاز به توضیح دارند به شکل زیر می‌باشند:
  1. IsActive : مشخص کننده‌ی این است که در این انجمن امکان ارسال تاپیک و پست وجود دارد و در صورت false بودن این خصوصیت، بر تمام زیر انجمن‌ها هم اعمال خواهد شد و برای زمانی مفید است که میخواهیم برای مدتی به هر دلیل خاصی امکان ارسال تاپیک و پست را برای انجمن خاصی، ندهیم. 
  2. IsColsed : خصوصت اولی که مطرح شد اگر مقدار false بگیرد، همچنان کاربران می‌توانند تایپک‌ها و پست‌های قبلی را مشاهده و مطالعه کنند. ولی با مقدار دهی این خصوصیت با مقدار false، امکان کلیه‌ی فعالیت‌ها و مشاهده‌ای را از محتوای این انجمن و زیر انجمن‌های آن نخواهیم داشت.
  3. IsPrivate : برای مواقعی که لازم است برای انجمن خاصی کلمه‌ی عبور در نظر بگیریم تا افراد خاص که کلمه‌ی عبور آن را دارند بتوانند در آن انجمن فعالیت کنند، در نظر گرفته شده است.
  4. ApprovedPostsCount , UnApprovedPostsCount,ApprovedTopicsCount,UnApprovedTopicsCount : برای بالا بردن کارآیی سیستم به مانند مدل‌های قبل در نظر گرفته شده‌اند.
  5. LastTopicId, LastTopicTitle , LastTopicCreator , LastTopicCreatorId , LastTopicCreatedOn: همچنین برای افزایش کارآیی سیستم و نمایش به عنوان قسمتی از مشخصات قابل مشاهده از هر انجمن، در نظر گرفته شده‌اند.
  6. Depth : برای نشان دادن عمق گره در درخت استفاده می‌شود که هنگام درج انجمن، این مورد از نتیجه‌ی جمع عمق پدر انتخاب شده و یک، به دست خواهد آمد. این مورد هنگام واکشی برای مثال 4 سطح اول برای نمایش آنها در صفحه‌ی اول انجمن به صورت سلسله مراتبی خیلی مفید خواهد بود.
  7. Path : برای استفاده از SQL Antipattern شمارش مسیر در نظر گرفته شده است. این مورد جزء Best Practice‌‌ها می‌تواند باشد. چون هم با استفاده از ساختار خود ارجاع، درخت خود را داریم و با این Antipattern کوئری‌های مربوط به درخت خیلی راحت خواهد بود.
  8. IsModeratorsInherited : اگر لازم است مدیران انجمن، پدر را به عنوان مدیر خود قبول کنند، این خصوصیت مقدار true خواهد گرفت.
  9. Subscribers : هر انجمنی می‌تواند یکسری مشترک نیز داشته باشد (به منظور اطلاع رسانی با درج یک تاپیک جدید در خود انجمن یا زیر انجمن‌های آن) .
  10. Posts : به منظور افزایش کارایی هنگام محاسبه تعداد پست‌های ارسالی در یک انجمن  ، در نظر گرفته شده است.
  11. TopicTrackers : در مقاله بعد توضیح داده خواهد شد.
  12. LastPostCreatedOn : به منظور استفاده از آن برای سیستم Tracking انجمن‌ها استفاده خواهد شد . 
ساختار درختی آن هم قابل مشاهده بوده  و نیاز به توضیح خاضی ندارد. در هر انجمن ما، یکسری تاپیک مطرح خواهد شد و برای این منظور لیستی از ForumTopic را در این کلاس معرفی کرده‌ایم. 
علاوه بر اینها انجمن‌های ما مدیرانی هم خواهند داشت که برای این منظور نیز لیستی از ForumModerator را در مدل بالا تعریف کرده‌ایم. همچنین تصمیم گرفتیم امکانی را برای سیستم انجمن در نظر بگیرم تا اگر لازم بود یک سری اعلان در بالای انجمن‌ها نشان داده شوند و در بخش مدیریت بتوان این امکان را هندل کرد؛ که لیستی  است از ForumAnnouncement‌های مدل بالا.

مدل مدیران انجمن
 /// <summary>
    /// Represents The Moderator For Forum
    /// </summary>
    public class ForumModerator
    {
        #region NavigationProperties
        /// <summary>
        /// gets or sets Forum
        /// </summary>
        public virtual Forum Forum { get; set; }
        /// <summary>
        /// gets or sets identifier of forum
        /// </summary>
        public virtual long ForumId { get; set; }
        /// <summary>
        /// gets or sets user that moderate forum
        /// </summary>
        public virtual User Moderator { get; set; }
        /// <summary>
        /// gets or sets id of user that moderate forum
        /// </summary>
        public virtual long ModeratorId { get; set; }
        /// <summary>
        /// gets or sets permission of user that moderate forum
        /// </summary>
        public virtual ForumModeratorPermissions Permissions { get; set; }
        /// <summary>
        /// indicate moderator's permissions in this forum apply with
        /// </summary>
        public virtual bool ApplyChildren { get; set; }
        #endregion
    }

    [Flags]
    public enum  ForumModeratorPermissions
    {
        CanEditPosts=1,
        CanDeletePosts=2,
        CanManageTopics=4,
        CanOpenCloseTopics=8,
       ...
    }
این مدل نشان می‌دهد که کاربر x به عنوان مدیر انجمن y می‌باشد و یکسری دسترسی‌ها را نیز در این انجمن خواهد داشت.
  1. ApplyChildren : برای اعمال دسترسی‌های مدیریتی کاربر x به زیر انجمن‌های انجمن y البته اگر خصوصیت IsModeratorsInherited زیر انجمن‌های مورد نظر با مقدار true مقدار دهی شده باشد.
  2. Permissions : از نوع ForumModeratorPermissions و نگهدارنده دسترسی‌های کاربر x به عنوان مدیر انجمن y، می‌باشد.
  3. نکته : برای این مدل آی دی در نظر گرفته نشده است و از کلید مرکب متشکل از ForumId و ModeratorId استفاده خواهیم کرد. 
مدل اعلان‌های انجمن
    /// <summary>
    /// Represents the Announcement that shown Top Of Forums
    /// </summary>
    public class ForumAnnouncement
    {
        #region Ctor
        /// <summary>
        /// create one instance of <see cref="ForumAnnouncement"/>
        /// </summary>
        public ForumAnnouncement()
        {
            Id = SequentialGuidGenerator.NewSequentialGuid();
           CreatedOn = DateTime.Now;
        }
        #endregion

        #region Properties
        /// <summary>
        /// gets or sets Identifier 
        /// </summary>
        public virtual Guid Id { get; set; }
        /// <summary>
        /// gets or sets DateTime That this Announcement Will be Shown
        /// </summary>
        public virtual DateTime StartOn { get; set; }
        /// <summary>
        /// gets or sets DateTime That this Announcement Will be Finished 
        /// </summary>
        public virtual DateTime? ExpireOn { get; set; }
        /// <summary>
        /// gets or sets Content of this Announcement
        /// </summary>
        public virtual string Message { get; set; }
        /// <summary>
        /// Indicate this Announcement Will be shown on Children Forums
        /// </summary>
        public virtual bool ApplyChildren { get; set; }
        /// <summary>
        /// gets or sets datetime that this record created
        /// </summary>
        public virtual DateTime CreatedOn { get; set; }
        #endregion

        #region NavigationProperties
        /// <summary>
        /// gets or sets Forum that associated With this Announcement
        /// </summary>
        public virtual Forum Forum { get; set; }
        /// <summary>
        /// gets or sets Identifier of Forum that associated With this Announcement
        /// </summary>
        public virtual long ForumId { get; set; }
        #endregion
    }
مدل بالا نشان دهنده‌ی اعلان‌هایی است که می‌توان با امکان تنظیم زمان آغاز و اتمام، آنها را در صفحات انجمن‌ها نمایش داد. این امکان هم از لحاظ مدیریتی می‌تواند مفید باشد و هم اگر لازم شد، تبلیغی انجام شود. برای اعمال رابطه‌ی یک به چند، یک خصوصیت از نوع Forum با همراه ForumId را در مدل بالا تعریف کرده‌ایم.
  1. ApplyChildren : برای مشخص کردین نمایش این اعلان در زیر انجمن‌های انجمن مورد نظر
  2. ExpireOn : به این دلیل نال پذیر در نظر گرفته شده است که اگر لازم بود، در زمان مشخصی به پایان نرسد و با null مقدار دهی شود.
کلاس پایه AuditBaseEntity 
 /// <summary>
    /// Represents a base class for AuditLog
    /// </summary>
    public abstract class AuditBaseEntity
    {
        #region Properties
        /// <summary>
        /// sets or gets identifier
        /// </summary>
        public virtual long Id { get; set; }
        /// <summary>
        /// gets or sets datetime that is created
        /// </summary>
        public  virtual DateTime CreatedOn { get; set; }
        /// <summary>
        /// gets or sets datetime that is modified
        /// </summary>
        public virtual DateTime? LastModifiedOn { get; set; }
        /// <summary>
        /// gets or sets reason of Last Update for increase performance
        /// </summary>
        public virtual string LastModifyReason { get; set; }
        /// <summary>
        /// gets or sets displayName of Last Modifier  for increase performance
        /// </summary>
        public virtual string LastModifier{ get; set; }
        /// <summary>
        /// indicate this entity is Locked for Modify
        /// </summary>
        public virtual bool ModifyLocked { get; set; }
        /// <summary>
        /// gets or sets rowversion for synchronization problem
        /// </summary>
        public virtual byte[] RowVersion { get; set; }
        /// <summary>
        /// gets or sets count of this content's Updates
        /// </summary>
        public virtual int ModifyCount { get; set; }
        #endregion

        #region NavigationProperties
        /// <summary>
        /// gets or sets creator of this record
        /// </summary>
        public virtual User Creator { get; set; }
        /// <summary>
        /// gets or sets creator's Id of this record
        /// </summary>
        public virtual long CreatorId { get; set; }
        #endregion
    }
این کلاس به منظور کپسوله کردن یکسری فیلد تکراری برای مدل‌هایی که نیاز است آخرین تغییر دهنده و زمان آن را ذخیره کنند، در نظر گرفته شده است و هدف از آن هیچ گونه اعمال ارث بری TPH یا TPT هم نیست.
ModifyLocked : برای زمانی مفید است که مدیریت امکان ویرایش یک مطلب را به صورت دستی غیرفعال میکند.
مدل تاپیک ها
  /// <summary>
    /// Represents the Topic in the Forums
    /// </summary>
    public class ForumTopic 
    {
        #region Ctor
        /// <summary>
        /// create one instance of <see cref="ForumTopic"/>
        /// </summary>
        public ForumTopic()
        {
            CreatedOn = DateTime.Now;
        }
        #endregion

        #region Properties
        /// <summary>
        /// sets or gets identifier
        /// </summary>
        public virtual long Id { get; set; }
        /// <summary>
        /// gets or sets datetime that is created
        /// </summary>
        public virtual DateTime CreatedOn { get; set; }
        /// <summary>
        /// gets or sets Title Of this topic
        /// </summary>
        public virtual string Title { get; set; }
        /// <summary>
        /// gets or sets name of tags that assosiated with 
        /// this content fo increase performance
        /// </summary>
        public virtual string TagNames { get; set; }
        /// <summary>
        /// indicate this topic is Sticky and will be shown top of forum
        /// </summary>
        public virtual bool IsSticky { get; set; }
        /// <summary>
        /// indicate this topic is closed
        /// </summary>
        public virtual bool IsClosed { get; set; }
        /// <summary>
        /// gets or sets identifier of  last post in this topic
        /// </summary>
        public virtual long LastPostId { get; set; }
        /// <summary>
        /// gets or sets identifier of Last user that post in this topic
        /// </summary>
        public virtual long LastPosterId { get; set; }
        /// <summary>
        /// gets or sets title of last Post in this topic
        /// </summary>
        public virtual string LastPostTitle { get; set; }
        /// <summary>
        /// gets or sets displayName of user that create lastpost in this topic
        /// </summary>
        public virtual string LastPoster { get; set; }
        /// <summary>
        /// gets or sets datetime that last post posted in this topic
        /// </summary>
        public virtual DateTime? LastPostCreatedOn { get; set; }
        /// <summary>
        /// indicate this topic is approved
        /// </summary>
        public virtual bool IsApproved { get; set; }
        /// <summary>
        /// indicate this topic is type of Announcements and shown in Annoucements sections
        /// </summary>
        public virtual bool IsAnnouncement { get; set; }
        /// <summary>
        /// gets or sets viewed count 
        /// </summary>
        public virtual long ViewCount { get; set; }
        /// <summary>
        /// gets or sets count of posts that they are approved
        /// </summary>
        public virtual int ApprovedPostsCount { get; set; }
        /// <summary>
        /// gets or sets count of posts that they are Unapproved
        /// </summary>
        public virtual int UnApprovedPostsCount { get; set; }
        /// <summary>
        /// gets or sets specifications of this topic's rating
        /// </summary>
        public virtual Rating Rating { get; set; }
        /// <summary>
        /// gets or sets datetime that this topic closed
        /// </summary>
        public virtual DateTime? ClosedOn { get; set; }
        /// <summary>
        /// gets or sets reason that this topic colsed
        /// </summary>
        public virtual string ClosedReason { get; set; }
        /// <summary>
        /// gets or sets count of reports
        /// </summary>
        public virtual int ReportsCount { get; set; }
        /// <summary>
        /// indicate the posts of this topic should be Moderate Before Dipslay
        /// </summary>
        public virtual bool ModeratePosts { get; set; }
        /// <summary>
        /// gets or sets Level of this topic
        /// </summary>
        public virtual ForumTopicLevel Level { get; set; }
        /// <summary>
        /// gets or sets type of this topic
        /// </summary>
        public virtual ForumTopicType Type { get; set; }
        #endregion

        #region NavigationProperties
        /// <summary>
        /// gets or sets Collection of tags that associated with this topic
        /// </summary>
        public virtual ICollection<Tag> Tags { get; set; }
        /// <summary>
        /// gets or sets forum
        /// </summary>
        public virtual Forum Forum { get; set; }
        /// <summary>
        /// gets or sets identifier of Forum
        /// </summary>
        public virtual long ForumId { get; set; }
        /// <summary>
        /// gets or sets Posts Of this topic
        /// </summary>
        public virtual ICollection<ForumPost> Posts { get; set; }
        /// <summary>
        /// get or set Subscriptions List 
        /// </summary>
        public virtual ICollection<User> Subscribers { get; set; }
        /// <summary>
        /// get or set Trackkers list of this Topic
        /// </summary>
        public virtual ICollection<ForumTopicTracker> Trackers { get; set; }
        /// <summary>
        /// gets or sets creator of this record
        /// </summary>
        public virtual User Creator { get; set; }
        /// <summary>
        /// gets or sets creator's Id of this record
        /// </summary>
        public virtual long CreatorId { get; set; }
        #endregion
    }

 public enum ForumTopicType
    {
        Non,
        Tutorial,
        Conversation,
        Question,
        News,
        Article
    }

    public enum ForumTopicLevel
    {
        Professional,
        Intermediate,
        Beginner
    }
مدل بالا مشخص کننده‌ی تاپیک‌های انجمن می‌باشد. خصوصیاتی که نیاز به توضیح دارند:
  1. LastPostId , LastPosterId, LastPoster  , LastPostTitle  ,LastPostCreatedOn: برای افزایش کارآیی سیستم در نظر گرفته شده‌اند.
  2. ModeratePosts : اگر لازم است پست‌های یک تاپیک خاص، قبل از نمایش مدیریت شوند، با true مقدار دهی خواهد شد.
  3. Tags : لیستی از برچسب‌ها که برای اعمال رابطه‌ی چند به چند با مدل برچسب‌های معرفی شده‌ی در مقاله اول، در نظر گرفته شده است.
  4. Posts : در هر تاپیکی یک سری پست به عنوان جواب‌های آن ارسال خواهد شد.
  5. Subscribers  : به مانند انجمن‌ها، تاپیک‌های ما هم می‌توانند یک سری مشترک داشته باشند، تا از تغییرات این تاپیک مطلع شوند .
  6. Trackers : مربوط به سیستم Tracking تاپیک میباشد. و در مقاله بعد توضیح داده خواهد شد.

 حتما لازم خواهد بود تاریخچه‌ی تغییرات برای  پست‌های ارسالی ذخیره شوند؛ در مقاله‌ی بعدی به این موضوع هم خواهیم پرداخت.
نتیجه این قسمت

نظرات مطالب
EF Code First #10
در رابطه با lazy loading سئوالی داشتم. در روش db first ، خود به خود navigation property‌ها در مدل ساخته میشه.از اونجایی که lazy loading به طور پیش فرض فعال هست ، اینطور که شما اینجا توضیح دادید هیچکدام از navigation property‌ها به جداول موردنظر رجوع نمی‌کنند. اگه تا اینجا رو درست گفته باشم سئوال اصلی من اینه:
وقتی جداول بزرگ باشند و تعداد navigation property‌ها زیاد، مخصوصا وقتی مراجعه به یک جدول چندبار اتفاق بیفتد ( مثلا فیلدهایی مثل InsertUserId و DeleteUserId داشته باشیم که هردو به جدول user مراجعه می‌کنند) EF نام‌های نامناسبی تولید میکنه که هنگام استفاده نمیشه تشخیص داد کدوم یکی مثلا به InsertUserId   و کدوم یکی به DeleteUserId مربوط میشه. اگر هم بخوایم دستی نامگذاری‌ها رو تغییر بدیم، علاوه بر وقتگیر بودن، با هربار تغییر مدل، دوباره باید اینکار رو تکرار کنیم. 
راه حلی که به ذهن من میرسه اینه که توی یه partial class، یه همچین property هایی اضافه کنم.(کد زیر) در واقع موقع نمایش در گرید، از InsertUsername به عنوان نام کاربری درج کننده استفاده می‌کنم. امیدوارم تونسته باشم درست توضیح بدم. می‌خوام بدونم این روش تا چه حد درسته.
public string InsertUsername 
{
    get { return DB.Users.Where(x=>x.Id == InsertUserId).Select(x=>x.Username).FirstOr Default(); } 
     private set {}
};

نظرات مطالب
ایجاد alert,confirm,prompt هایی متفاوت با jQuery Impromptu
سلام
خسته نباشید
من توی سایتم از ajax استفاده کردم و برای حذف یه رکورد اول از کاربر تاییدیه گرفتم . اگه کاربر روی دکمه‌ی بله کلیک کنه ، عملیات شروع میشه و رکورد حذف میشه .
آخر سر توی تابعی که نتیجه برمیگرده توش (handleServerResponse)، بر اساس نتیجه‌ی برگشتی پیغام مناسبی بایدنمایش داده بشه. اما هیچ پیغامی نشون داده نمیشه
خیلی چیزا رو آزمایش کردم و آخر سر به این نتیجه رسیدم که چون دو تا پیغام میخواد پشت سر هم نمایش داده بشه ، این مشکل پیش میاد :(
حتی وقتی که پیغام تایید حذف رو برداشتم ، دیدم که پیغام دوم میاد !
واااقعا نمیدونم علتش چیه ! ممنون میشم کمکم کنید.
اینم قسمتی از کدمه :
function deleteFile(location, filename)//حذف فیزیکی و منطقی فایل
{
    $.prompt("آیا برای حذف فایل موجود اطمینان دارید ؟", {
        title: '<img src="Images/warning.png" />',
        buttons: { "بله": true, "خیر": false },
        focus: 2,
        submit: function (e, v, m, f) {
            if (v == true) {
                var getdate = new Date();  //Used to prevent caching during ajax call
                if (xmlhttp) {
                    $("p#vtip").fadeOut("slow").remove(); //محو شدن tooltip
                    var id = document.getElementById("id").value;
                    var i = '3';
                    params = "id=" + id + "&i=" + i + "&filename=" + filename + "&location=" + location;
                    xmlhttp.open("POST", "FetchData/dbSearchDocument1.php", true);
                    xmlhttp.onreadystatechange = handleServerResponse4;
                    xmlhttp.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
                    xmlhttp.send(params);
                }
            }
            else {
            }
        }
    });
}
//-----------------------------------------------------------------------
function handleServerResponse4() {
    if ((xmlhttp.readyState == 1) || (xmlhttp.readyState == 2) || (xmlhttp.readyState == 3))//loading
    {
        document.getElementById("content").innerHTML = "<img src='Images/bigloading.gif'/>";
    }
    else if (xmlhttp.readyState == 4) //ready
    {
        if (xmlhttp.status == 200) {
            if (xmlhttp.responseText == 1) {
                $.prompt(". حذف فایل با موفقیت انجام شد ", {
                    title: '<img src="Images/warning3.png" />',
                    buttons: { "بستن": true }
                });
            }
            else {
                $.prompt("! حذف فایل با خطا مواجه شده است", {
                    title: '<img src="Images/warning2.png" />',
                    buttons: { "بستن": true }
                });
            }
        }
        else {
            alert("Error during AJAX call. Please try again");
        }
    }
}
//-----------------------------------------------------------------------
مطالب
نحوه‌ی بستن یک بازه‌ی IP در IIS
یک سری از ربات‌ها مدام سایت‌ها را برای یافتن یک سری از اسکریپت‌های خاص اسکن می‌کنند. IPهای آن‌ها نیز عموما متعلق است به چین و هسایگان آن. مشکلی که با این ربات‌ها وجود دارد این است که از یک IP خاص نشات نمی‌گیرند و به نظر صدها سرور آلوده را جهت مقاصد خود مورد استفاده قرار می‌دهند. به همین جهت نیاز است بتوان یک بازه‌ی IP را در IIS بست.

بستن یک بازه‌ی IP در IIS 6

در IIS6 باید به خواص وب سایت و برگه‌ی Directory security آن مراجعه کرد. سپس در این قسمت، در حین افزودن IP مد نظر، باید گزینه‌ی Group of computers را انتخاب نمود.


در اینجا برای مثال برای بستن IPهایی که با 194 شروع می‌شوند، باید 194.0.0.0 را وارد کرد و در این حالت subnet mask را نیز باید 255.0.0.0 انتخاب کرد. با این subnet mask خاص، اعلام می‌کنیم که فقط اولین قسمت IP وارد شده برای ما اهمیت دارد و سه قسمت بعدی خیر. به این ترتیب تمام IPهای شروع شده با 194 با هر سه جزء دیگر دلخواهی، بسته خواهند شد.


بستن یک بازه‌ی IP در IISهای 7 به بعد

در IISهای 7 به بعد نیاز است مراحل زیر را طی کرده و نقش «IP and Domain Restrictions » را نصب کنید.
 Administrative Tools -> Server Manager -> expand Roles
-> Web Server (IIS) ->  Role Services -> Add Role Services -> select IP and Domain Restrictions
پس از آن با استفاده از تنظیمات ذیل در فایل web.config می‌توان یک IP و یا بازه‌ای از IPها را بست:
   <system.webServer>
      <security>
         <ipSecurity>
            <add ipAddress="192.168.100.1" />
            <add ipAddress="169.254.0.0" subnetMask="255.255.0.0" />
         </ipSecurity>
      </security>
   </system.webServer>
البته علاوه بر نصب نقش یاد شده، باید Feature Delegation نیز جهت استفاده از آن فعال گردد:
 IIS 7 -> root server -> Feature Delegation -> IP and Domain Restrictions
->  Change the delegation to Read/Write
مطالب
PowerShell 7.x - قسمت هشتم - ماژول‌ها
توسط ماژول‌ها میتوانیم یک مجموعه از دستورات را گروه‌بندی کنیم و تحت عنوان یک پکیج ارائه دهیم که برای دیگران نیز قابل استفاده باشند. برای ایجاد یک ماژول کافی است اسکریپت‌های خود را درون یک فایل با پسوند psm1 قرار دهیم؛ به این فایل اصطلاحاً root module گفته میشود. در واقع میتوان گفت ماژول‌ها یک روش مناسب برای به اشتراک‌گذاری اسکریپت‌ها میباشند. تا اینجا با کمک پروفایل‌ها توانستیم امکان استفاده مجدد از توابع و اسکریپت‌ها را داشته باشیم؛ ماژول‌ها نیز یک روش دیگر برای بارگذاری اسکریپت‌ها درون شل هستند. زمانیکه شل را باز میکنیم PowerShell به صورت خودکار یکسری مسیر را برای بارگذاری ماژول‌ها اسکن میکند. توسط متغیر env:PSModulePath$ میتوانیم لیست این مسیرها را ببینیم:  
PS /> $env:PSModulePath -Split ":"

/Users/sirwanafifi/.local/share/powershell/Modules
/usr/local/share/powershell/Modules
/usr/local/microsoft/powershell/7/Modules
همانطور که عنوان شد برای ایجاد یک ماژول کافی است اسکریپت‌های خود را داخل یک فایل با پسوند psm1 ذخیره کنیم. به عنوان مثال میتوانیم تابع Get-PingReply را درون یک فایل با نام PingModule.psm1 ذخیره و سپس توسط دستور Import-Module ماژول را ایمپورت کنیم:  
PS /> Import-Module ./PingModule.psm1
سپس توسط دستور Get-Module PingModule میتوانیم جزئیات ماژول ایمپورت شده را مشاهده نمائیم: 
PS /> Get-Module PingModule

ModuleType Version    PreRelease Name                                ExportedCommands
---------- -------    ---------- ----                                ----------------
Script     0.0                   PingModule                          Get-PingReply
به صورت پیش‌فرض تمام توابع درون اسکریپت export خواهند شد. اگر ExportedCommands خالی باشد به این معنا است که ماژول به درستی ایمپورت نشده‌است. به عنوان مثال اگر سعی کنید فایل قبل را با پسوند ps1 به عنوان ماژول ایمپورت کنید. خطایی هنگام ایمپورت کردن مشاهده نخواهید کرد و قسمت ExportedCommands خالی خواهد بود. در این‌حالت نیز امکان استفاده از تابع درون اسکریپت را خواهیم داشت؛ اما هیچ تضمینی نیست که به صورت یک ماژول به درستی عمل کند. بنابراین بهتر است ماژول‌هایی که ایجاد میکنیم حتماً پسوند psm1 داشته باشند.
PS /> Import-Module ./PingModule.ps1
PS /> Get-Module PingModule

ModuleType Version    PreRelease Name                                ExportedCommands
---------- -------    ---------- ----                                ----------------
Script     0.0                   PingModule
ممکن است بخواهیم یکسری توابع را به صورت private تعریف کنیم و فقط تعداد محدودی از توابع به صورت public باشند. در این حالت میتوانیم درون فایل psm1 با کمک دستور Export-ModuleMember اینکار را انجام دهیم: 
Function Get-PingReply {
    // as before
}

Function Get-PrivateFunction {
    Write-Debug 'This is a private function'
}

Export-ModuleMember -Function @(
    'Get-PingReply'
)
اضافه کردن متادیتا به ماژول‌ها
برای هر ماژول میتوانیم با کمک module manifest یکسری متادیتا به ماژول اضافه کنیم. این متادیتا درون یک فایل با پسوند psd1 که محتویات آن در واقع یک Hashtable است ذخیره میشود. برای ایجاد فایل manifest میتوانیم از دستور New-ModuleManifest استفاده کنیم. به عنوان مثال برای ایجاد فایل manifest برای ماژول PingModule.psm1 اینگونه عمل خواهیم کرد:  
$moduleSettings = @{
    Path                 = './PingModule.psd1'
    Description          = 'A module to ping a remote system'
    RootModule           = 'PingModule.psm1'
    ModuleVersion        = '1.0.0'
    FunctionsToExport    = @(
        'Get-PingReply'
    )
    PowerShellVersion    = '5.1'
    CompatiblePSEditions = @(
        'Core'
        'Desktop'
    )
}
New-ModuleManifest @moduleSettings
تنها پراپرتی موردنیاز برای ایجاد module manifest پراپرتی Path میباشد. این پراپرتی به فایلی که متادیتا قرار است درون آن ایجاد شود اشاره دارد. همانطور که مشاهده میکنید یکسری پراپرتی دیگر نیز اضافه کرده‌ایم و توسط splatted hash table (با کمک @) به دستور New-ModuleManifest ارسال کرده‌ایم. به این معنا که کلیدها و مقادیر درون hash table به جای اینکه یکجا به عنوان یک آبجکت به دستور موردنظر ارسال شوند، به صورت جدا پاس داده شده‌اند. اما اگر از $ استفاده میکردیم: 
$moduleSettings = @{
    Author = 'John Doe'
    Description = 'This is a sample module'
}

New-ModuleManifest $moduleSettings
با خطای زیر مواجه میشدیم: 
New-ModuleManifest : A parameter cannot be found that matches parameter name 'Author'.
در نهایت فایل manifest در مسیر تعیین شده ایجاد خواهد شد: 
#
# Module manifest for module 'PingModule'
#
# Generated by: sirwanafifi
#
# Generated on: 01/01/2023
#

@{

    # Script module or binary module file associated with this manifest.
    RootModule           = './PingModule.psm1'

    # Version number of this module.
    ModuleVersion        = '1.0.0'

    # Supported PSEditions
    CompatiblePSEditions = 'Core', 'Desktop'

    # ID used to uniquely identify this module
    GUID                 = '3f8561fc-c004-4c8e-b2fc-4a4191504131'

    # Author of this module
    Author               = 'sirwanafifi'

    # Company or vendor of this module
    CompanyName          = 'Unknown'

    # Copyright statement for this module
    Copyright            = '(c) sirwanafifi. All rights reserved.'

    # Description of the functionality provided by this module
    Description          = 'A module to ping a remote system'

    # Minimum version of the PowerShell engine required by this module
    PowerShellVersion    = '5.1'

    # Name of the PowerShell host required by this module
    # PowerShellHostName = ''

    # Minimum version of the PowerShell host required by this module
    # PowerShellHostVersion = ''

    # Minimum version of Microsoft .NET Framework required by this module. This prerequisite is valid for the PowerShell Desktop edition only.
    # DotNetFrameworkVersion = ''

    # Minimum version of the common language runtime (CLR) required by this module. This prerequisite is valid for the PowerShell Desktop edition only.
    # ClrVersion = ''

    # Processor architecture (None, X86, Amd64) required by this module
    # ProcessorArchitecture = ''

    # Modules that must be imported into the global environment prior to importing this module
    # RequiredModules = @()

    # Assemblies that must be loaded prior to importing this module
    # RequiredAssemblies = @()

    # Script files (.ps1) that are run in the caller's environment prior to importing this module.
    # ScriptsToProcess = @()

    # Type files (.ps1xml) to be loaded when importing this module
    # TypesToProcess = @()

    # Format files (.ps1xml) to be loaded when importing this module
    # FormatsToProcess = @()

    # Modules to import as nested modules of the module specified in RootModule/ModuleToProcess
    # NestedModules = @()

    # Functions to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no functions to export.
    FunctionsToExport    = 'Get-PingReply'

    # Cmdlets to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no cmdlets to export.
    CmdletsToExport      = '*'

    # Variables to export from this module
    VariablesToExport    = '*'

    # Aliases to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no aliases to export.
    AliasesToExport      = '*'

    # DSC resources to export from this module
    # DscResourcesToExport = @()

    # List of all modules packaged with this module
    # ModuleList = @()

    # List of all files packaged with this module
    # FileList = @()

    # Private data to pass to the module specified in RootModule/ModuleToProcess. This may also contain a PSData hashtable with additional module metadata used by PowerShell.
    PrivateData          = @{

        PSData = @{

            # Tags applied to this module. These help with module discovery in online galleries.
            # Tags = @()

            # A URL to the license for this module.
            # LicenseUri = ''

            # A URL to the main website for this project.
            # ProjectUri = ''

            # A URL to an icon representing this module.
            # IconUri = ''

            # ReleaseNotes of this module
            # ReleaseNotes = ''

            # Prerelease string of this module
            # Prerelease = ''

            # Flag to indicate whether the module requires explicit user acceptance for install/update/save
            # RequireLicenseAcceptance = $false

            # External dependent modules of this module
            # ExternalModuleDependencies = @()

        } # End of PSData hashtable

    } # End of PrivateData hashtable

    # HelpInfo URI of this module
    # HelpInfoURI = ''

    # Default prefix for commands exported from this module. Override the default prefix using Import-Module -Prefix.
    # DefaultCommandPrefix = ''

}
هر ماژول باید یک آی‌دی منحصر به فرد داشته باشد که به صورت Guid توسط یک پراپرتی با همین نام تعیین میشود. برای هر پراپرتی درون این فایل توضیحی به صورت کامنت نوشته شده است؛ اما برای دیدن جزئیات کامل میتوانید به اینجا مراجعه نمائید. در اینجا RootModule به PingModule.psm1 تنظیم شده است. تنظیم این پراپرتی نحوه نمایش module type را در خروجی Get-Module مشخص میکند. این مقدار اگر به فایل ماژول (psm1) اشاره کند، نوع ماژول script در نظر گرفته میشود، اگر به یک DLL اشاره کند به binary و در نهایت اگر به یک فایلی با پسوند دیگری اشاره کند manifest در نظر گرفته میشود. همچنین درون فایل فوق یکسری پراپرتی مانند CmdletsToExport, VariablesToExport, AliasesToExport به صورت خودکار تنظیم شده‌اند. در نهایت برای تست صحت فایل میتوانیم از دستور Test-ModuleManifest استفاده کنیم: 
PS /> Test-ModuleManifest ./PingModule.psd1   

ModuleType Version    PreRelease Name                                ExportedCommands
---------- -------    ---------- ----                                ----------------
Script     1.0.0                 PingModule                          Get-PingReply
پابلیش ماژول
در نهایت توسط Publish-Module میتوانیم ماژول موردنظرمان را درون یک مخزن PowerShell پابلیش کنیم. رایج‌ترین مخزن برای انتشار پکیج‌های PowerShell یک فید نیوگت (مانند PowerShell Gallery) است. همچنین میتوانیم یک مخزن لوکال نیز برای پابلیش پکیج‌ها نیز ایجاد کنیم و خروجی نهایی را به صورت یک فایل با پسوند nupkg با دیگران به اشتراک بگذاریم. برای اینکار ابتدا نیاز است یک مخزن لوکال را به PowerShell معرفی کنیم: 
PS /> Register-PSRepository -Name 'PSLocal' `                       
>>     -SourceLocation "$(Resolve-Path $RepoPath)" `
>>     -PublishLocation "$(Resolve-Path $RepoPath)" `
>>     -InstallationPolicy 'Trusted'
در کد فوق میتوانید مقدار متغیر RepoPath را به محل مدنظر روی سیستم‌تان تنظیم کنید. ساختار ماژولی که میخواهیم پابلیش کنیم نیز اینچنین خواهد بود: 
ProjectRoot 
| -- PingModule
     | -- PingModule.psd1 
     | -- PingModule.psm1
در ادامه برای پابلیش ماژول فوق درون ProjectRoot دستور Publish-Module را اینگونه اجرا خواهیم کرد: 
PS /> Publish-Module -Path ./PingModule/ -Repository PSLocal
خروجی دستور فوق یک فایل با پسوند nupkg در مسیر مخزنی است که معرفی کردیم. همچنین با کمک دستور Find-Module میتوانیم ماژول موردنظر را لیست کنیم: 
PS /> Find-Module -Name PingModule -Repository PSLocal

Version              Name                                Repository           Description
-------              ----                                ----------           -----------
0.0.1                PingModule                          PSLocal              Get-PingReply is a.
برای نصب ماژول روی یک سیستم دیگر نیز ابتدا باید مطمئن شوید که سیستم مقصد، مخزن لوکال را تعریف کرده باشد و در نهایت توسط دستور Install-Module میتوانیم ماژول موردنظر را نصب کنیم: 
PS /> Install-Module -Name PingModule -Repository PSLocal -Scope CurrentUser
همچنین امکان پابلیش کردن ورژن‌های متفاوت را نیز خواهیم داشت: 
ProjectRoot 
| -- PingModule
     | -- 1.0.0
          | -- PingModule.psd1 
          | -- PingModule.psm1
     | -- 1.0.1
          | -- PingModule.psd1 
          | -- PingModule.psm1
برای پابلیش کردن هر کدام از ماژول‌های فوق باید به ازای هر کدام یکبار دستور Publish-Module را اجرا کنیم: 
PS /> Publish-Module -Path ./PingModule/1.0.0 -Repository PSLocal
PS /> Publish-Module -Path ./PingModule/1.0.1 -Repository PSLocal
اکنون حین نصب ماژول میتوانیم ورژن را نیز تعیین کنیم؛ در غیراینصورت آخرین ورژن یعنی 1.0.1 نصب خواهد شد: 
PS /> Install-Module -Name PingModule -Repository PSLocal -Scope CurrentUser
PS /> Get-InstalledModule -Name PingModule                                                                            
Version              Name                                Repository           Description
-------              ----                                ----------           -----------
1.0.1                PingModule                          PSLocal              Get-PingReply is a.
ساختار مناسب برای ایجاد ماژول‌ها
برای توسعه ماژول‌های PowerShell توصیه میشود که از ساختار Multi-file layout استفاده شود به این معنا که بخش‌های مختلف پروژه به قسمت‌های کوچک‌تر و قابل‌نگهداری تقسیم شوند: 
ProjectRoot 
| -- PingModule
     | -- 1.0.0
          | -- Public/
          | -- Private/
          | -- PingModule.psd1
          | -- PingModule.psm1
در اینحالت باید اسکریپت‌ها و فایل‌های موردنیاز را توسط dot sourcing درون فایل ماژول بارگذاری کنیم: 
$ScriptList = Get-ChildItem -Path $PSScriptRoot/Public/*.ps1 -Filter *.ps1

foreach ($Script in $ScriptList) {
    . $Script.FullName
}

$ScriptList = Get-ChildItem -Path $PSScriptRoot/Private/*.ps1 -Filter *.ps1

foreach ($Script in $ScriptList) {
    . $Script.FullName
}
یک مثال عملی
در ادامه میخواهیم یک ماژول تهیه کنیم که قابلیت امضاء روی PDF را با کمک کتابخانه iTextSharp.LGPLv2.Core انجام دهیم و به شکل زیر برای کاربر قابل استفاده باشد: 
PS /> Set-PDFSingature -PdfToSign "./sample_invoice.pdf" -SignatureImage "./sample_signature.jpg"
بنابراین ساختار پروژه را اینگونه ایجاد خواهیم کرد: 
ProjectRoot 
| -- SignPdf
     | -- 1.0.0
          | -- Public/
               | -- dependencies/
                    | -- BouncyCastle.Crypto.dll
                    | -- System.Drawing.Common.dll
                    | -- Microsoft.Win32.SystemEvents.dll
                    | -- iTextSharp.LGPLv2.Core.dll
               | -- Set-PDFSingature.ps1
          | -- SignPdf.psd1
          | -- SignPdf.psm1
برای استفاده از پکیج ذکر شده نیاز خواهد بود که Dllهای موردنیاز را نیز به عنوان وابستگی به پروژه اضافه کنیم (محتویات پوشه dependencies) سپس درون فایل ماژول (SignPdf.psm1) همانطور که عنوان شد میبایست اسکریپت‌ها درون پوشه Public را بارگذاری کنیم و همچنین درون تابعی که قرار است Export شود را نیز تعیین کرده‌ایم (Set-PDFSingature) 
$ScriptList = Get-ChildItem -Path $PSScriptRoot/Public/*.ps1 -Filter *.ps1

foreach ($Script in $ScriptList) {
    . $Script.FullName
}

Export-ModuleMember -Function Set-PDFSingature
در ادامه درون فایل Set-PDFSignature پیاده‌سازی را انجام خواهیم داد: 
using namespace iTextSharp.text
using namespace iTextSharp.text.pdf
using namespace System.IO

Function Set-PDFSingature {
    [CmdletBinding()]
    Param(
        [Parameter(Mandatory = $true, ValueFromPipeline = $true)]
        [ValidateScript({
                if (Test-Path ([Path]::Join($(Get-Location), $_))) {
                    return $true
                }
                else {
                    throw "Signature image not found"
                }
                if ($_.EndsWith('.pdf')) {
                    return $true
                }
                else {
                    throw "File extension must be .pdf"
                }
            })]
        [string]$PdfToSign,
        [Parameter(Mandatory = $true, ValueFromPipeline = $true)]
        [ValidateScript({
                if (Test-Path ([Path]::Join($(Get-Location), $_))) {
                    return $true
                }
                else {
                    throw "Signature image not found"
                }
                if ($_.EndsWith('.png') -or $_.EndsWith('.jpg')) {
                    return $true
                }
                else {
                    throw "File extension must be .png or .jpg"
                }
            })]
        [string]$SignatureImage,
        [Parameter(Mandatory = $false, ValueFromPipeline = $true)]
        [int]$XPos = 130,
        [Parameter(Mandatory = $false, ValueFromPipeline = $true)]
        [int]$YPos = 50
    )
    Try {
        Add-Type -Path "$PSScriptRoot/dependencies/*.dll"
        $pdf = [PdfReader]::new("$(Get-Location)/$PdfToSign")
        $fs = [FileStream]::new("$(Get-Location)/$PdfToSign-signed.pdf", 
            [FileMode]::Create)
        $stamper = [PdfStamper]::new($pdf, $fs)
        $stamper.AcroFields.AddSubstitutionFont([BaseFont]::CreateFont())
        $content = $stamper.GetOverContent(1)
        $width = $pdf.GetPageSize(1).Width
        $image = [Image]::GetInstance("$(Get-Location)/$SignatureImage")
        $image.SetAbsolutePosition($width - $XPos, $YPos)
        $image.ScaleAbsolute(100, 30)
        $content.AddImage($image)
        $stamper.Close()
        $pdf.Close()
        $fs.Dispose()
    }
    Catch {
        Write-Host "Error: $($_.Exception.Message)"
    }
}
روال قرار دادن یک امضاء بر روی یک فایل PDF قبلاً در سایت توضیح داده شده است. کدهای فوق در واقع معادل PowerShell همان کدهای موجود در سایت هستند و نکته خاصی ندارند. در نهایت میتوانیم ماژول تهیه شده را روی مخزن موردنظر پابلیش کنیم: 
PS /> Publish-Module -Path ./SignPdf/1.0.0 -Repository PSLocal
برای نصب ماژول میتوانیم از دستور Install-Module استفاده کنیم: 
PS /> Install-Module -Name SignPdf -Repository PSLocal -Scope CurrentUser
در نهایت برای استفاده از ماژول ایجاد شده میتوانیم اینگونه عمل کنیم: 
PS /> Set-PDFSingature -PdfToSign "./sample_invoice.pdf" -SignatureImage "./sample_signature.jpg"
خروجی نیز فایل امضاء شده خواهد بود: 

کدهای ماژول را میتوانید از اینجا دانلود کنید. 

نظرات مطالب
ارتقاء به ASP.NET Core 1.0 - قسمت 13 - معرفی View Components
- همانطور که در مقدمه‌ی بحث هم عنوان شد، مفهوم Child Actions از نگارش Core حذف شده‌است؛ چون مشکلات زیادی دارد (به این لیست، مشکلات async و همچنین آغاز یک چرخه‌ی جدید MVC را هم اضافه کنید که کارآیی مناسبی ندارد و یک سربار به شمار می‌رود).
- جایگزین آن ViewComponet است و از لحاظ دسترسی به منابع و سرویس‌ها محدودیتی ندارد. یک مثال
- هنوز هم اگر صرفا نیاز به رندر یک پارشال View را به صورت Ajax ایی دارید، روش زیر کار می‌کند:
جایی که می‌خواستید Html.RenderAction را قرار دهید، قطعه کد Ajax ایی زیر را فراخوانی کنید:
<div id="dynamicContentContainer"></div>
<script>   
    $.get('@Url.Action("GetData", "Home")', {id : 1}, function(content){
            $("#dynamicContentContainer").html(content);
        });
</script>
کار آن دریافت محتوای html ایی اکشن متد ذیل و افزودن آن به div مشخص شده‌است.
[HttpGet]
public IActionResult GetData(int id)
{
   return PartialView(id);
}
با این پارشال View فرضی:
@model int 
<span>Values from controler :</span> @Model