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

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

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

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


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

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


شروع کار با iTextSharp

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

            var fileStream = new FileStream("card.pdf", FileMode.Create, FileAccess.Write, FileShare.None);

            var docFont = GetFont();

            var pageSize = PageSize.A6.Rotate(); // سایز کارت را اینجا باید مشخص کرد

            var doc = new Document(pageSize);

            doc.SetMargins(18f, 18f, 15f, 2f);

            var pdfWriter = PdfWriter.GetInstance(doc, fileStream);

            doc.Open();
-  در اینجا سایز کارت، بر روی کاغذ A6 در حالت افقی قرار داده شده است. بدیهی است که مطابق نیاز خودتان می‌توانید این سایز را تغییر دهید.
-  تابع GetFont یک تابع کمکی است که در سورس نهایی ارائه شده است و نکته تعریف فونت در iTextSharp  در آن رعایت شده است.
 - بقیه موارد نیز جزء الزامات کار با این کتابخانه است.


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

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

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

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

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

همانطور که از تگ Table در HTML می‌توان برای رسم جدول و قرار دادن عناصر در سطر و ستون‌های آن استفاده کرد، در iText نیز می‌توان از کلاس PdfPTable برای ترسیم جدول و از متد AddCell آن برای افزودن سلول به آن استفاده کرد. کار با کلاس PdfPTable نیز ساده است. کافی هست به هنگام ساخت نمونه‌ای از آن، در سازنده‌اش تعداد ستون‌های جدول را ذکر کنید. سپس با استفاده از متد AddCell آن، پارامتری از جنس PdfPCell برای آن ارسال کنید تا به جدول، سلول جدیدی اضافه شود و در صورتیکه تعداد سلول‌های جدید، از تعداد ستون‌های تعریف شده بیشتر شود، iText به صورت خودکار سلول‌های اضافی را به ردیف جدیدی منتقل می‌کند. البته برای افزودن سطر و ستون، روش‌های دیگری نیز هست؛ ولی گویا  روش مرجح همین روش است.  کلاس PdfPCell که نقش سلول‌های جدول را بازی می‌کند، نیز می‌تواند شامل متن، تصویر و یا حتی یک جدول تودرتو باشد.
  
در ادامه کدهای جدول بالایی کارت شامل لوگوی دانشگاه، عنوان مسابقات و عکس فرد را مشاهده می‌کنید:
            // جدولی که برای چیدمان عناصر ارم دانشگاه و عنوان و عکس شخص استفاده می‌شود
            var topTable = new PdfPTable(3)
            {
                WidthPercentage = 100,
                RunDirection = PdfWriter.RUN_DIRECTION_RTL,
                ExtendLastRow = false,
            };

            var universityLogoImage = Image.GetInstance(universityLogoPath);

            universityLogoImage.ScaleAbsolute(70, 100);

            topTable.AddCell(new PdfPCell(universityLogoImage)
            {
                HorizontalAlignment = Element.ALIGN_LEFT,
                Border = 0
            });

            topTable.AddCell(new PdfPCell(new Phrase("کارت مسابقات دانشگاه آزاد اسلامی", docFont))
            {
                RunDirection = PdfWriter.RUN_DIRECTION_RTL,
                HorizontalAlignment = Element.ALIGN_CENTER,
                Border = 0,
            });

            var userImage = Image.GetInstance(userModel.ImagePath);
            userImage.Border = Rectangle.TOP_BORDER | Rectangle.RIGHT_BORDER | Rectangle.BOTTOM_BORDER | Rectangle.LEFT_BORDER;
            userImage.BorderWidth = 1f;
            userImage.BorderColor = new BaseColor(204, 204, 204); // gray color
            userImage.ScaleAbsolute(70, 100);

            topTable.AddCell(new PdfPCell(userImage)
            {
                HorizontalAlignment = 2,
                Border = 0
            });

            int[] topTableColumnsWidth = { 10, 25, 10 };

            topTable.SetWidths(topTableColumnsWidth);

            doc.Add(topTable);
-  در ابتدا یک جدول سه ستونه تعریف شده است. تعداد ستون‌ها در هنگام نمونه سازی از کلاس PdfPTable، در سازنده‌ی آن ذکر شده است.
-  در iText برای کار با تصاویر، باید از کلاس Image و متد GetInstance فراهم شده توسط خود کتابخانه استفاده کرد. سپس این تصویر را باید به عنوان پارامتر به سلول جدول ارسال کرد.
-  به کمک متد AddCell، می‌توان به جدول، سلول اضافه کرد و به صورت خودکار سلول‌های جدیدی که از تعداد ستون‌ها بیشتر می‌شوند، به سطر جدید منتقل می‌شوند.
-  اگر می‌خواهید در سلولی متنی نمایش دهید، از کلاس Phrase و تعیین صریح فونت آن استفاده کنید؛ چرا که در غیر این صورت ممکن است متون فارسی نمایش داده نشود.
-  در انتها هم جدول مورد نظر را باید به شی doc از جنس کلاس Document تعریف شده اضافه کرد.
 
بدیهی هست که اطلاعات شخص مثل نام، نام خانوادگی و ... را نیز باید در یک جدول چهار ستونه قرار داد و نکته‌ی خاص اضافه‌تری ندارد. 

حال اگر بیاییم این تکه کدها را کنار هم قرار بدهیم به کدی قابل اجرا خواهیم رسید.
ابتدا کلاسی را که در برگیرنده‌ی اطلاعات فرد است، تعریف می‌کنیم:
    public class UserModel
    {
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public string StudentNumber { get; set; }
        public string NationalCode { get; set; }
        public string UniversityName { get; set; }
        public string ImagePath { get; set; }
    }
 
- سپس کلاس CardReport را که اصل و اساس بحث ما بود، تعریف می‌کنیم.
using System;
using System.IO;
using iTextSharp.text;
using iTextSharp.text.pdf;
using Font = iTextSharp.text.Font;
using Image = iTextSharp.text.Image;
using Rectangle = iTextSharp.text.Rectangle;

namespace ITextSharpCardSample
{
    public class CardReport
    {
        public static void Generate(UserModel userModel, string competitionImagePath, string universityLogoPath)
        {
            var fileStream = new FileStream("card.pdf", FileMode.Create, FileAccess.Write, FileShare.None);

            var docFont = GetFont();

            var pageSize = PageSize.A6.Rotate(); // سایز کارت را اینجا باید مشخص کرد

            var doc = new Document(pageSize);

            doc.SetMargins(18f, 18f, 15f, 2f);

            var pdfWriter = PdfWriter.GetInstance(doc, fileStream);

            doc.Open();

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


            // جدولی که برای چیدمان عناصر ارم دانشگاه و عنوان و عکس شخص استفاده می‌شود
            var topTable = new PdfPTable(3)
            {
                WidthPercentage = 100,
                RunDirection = PdfWriter.RUN_DIRECTION_RTL,
                ExtendLastRow = false,
            };

            var universityLogoImage = Image.GetInstance(universityLogoPath);

            universityLogoImage.ScaleAbsolute(70, 100);

            topTable.AddCell(new PdfPCell(universityLogoImage)
            {
                HorizontalAlignment = Element.ALIGN_LEFT,
                Border = 0
            });

            topTable.AddCell(new PdfPCell(new Phrase("کارت مسابقات دانشگاه آزاد اسلامی", docFont))
            {
                RunDirection = PdfWriter.RUN_DIRECTION_RTL,
                HorizontalAlignment = Element.ALIGN_CENTER,
                Border = 0,
            });

            var userImage = Image.GetInstance(userModel.ImagePath);
            userImage.Border = Rectangle.TOP_BORDER | Rectangle.RIGHT_BORDER | Rectangle.BOTTOM_BORDER | Rectangle.LEFT_BORDER;
            userImage.BorderWidth = 1f;
            userImage.BorderColor = new BaseColor(204, 204, 204); // gray color
            userImage.ScaleAbsolute(70, 100);

            topTable.AddCell(new PdfPCell(userImage)
            {
                HorizontalAlignment = 2,
                Border = 0
            });

            int[] topTableColumnsWidth = { 10, 25, 10 };

            topTable.SetWidths(topTableColumnsWidth);

            doc.Add(topTable);


            // جدول مشخصات شرکت کننده مثل نام و نام خانوادگی
            var infoTable = new PdfPTable(4)
            {
                WidthPercentage = 100,
                RunDirection = PdfWriter.RUN_DIRECTION_RTL,
                ExtendLastRow = false,
                SpacingBefore = 15,
            };

            infoTable.AddCell(new PdfPCell(new Phrase("نام:", docFont))
            {
                RunDirection = PdfWriter.RUN_DIRECTION_RTL,
                HorizontalAlignment = Element.ALIGN_LEFT,
                Border = 0,
                PaddingBottom = 15
            });

            infoTable.AddCell(new PdfPCell(new Phrase(userModel.FirstName, docFont))
            {
                RunDirection = PdfWriter.RUN_DIRECTION_RTL,
                HorizontalAlignment = Element.ALIGN_LEFT,
                Border = 0
            });

            infoTable.AddCell(new PdfPCell(new Phrase("نام خانوادگی:", docFont))
            {
                RunDirection = PdfWriter.RUN_DIRECTION_RTL,
                HorizontalAlignment = Element.ALIGN_LEFT,
                Border = 0
            });

            infoTable.AddCell(new PdfPCell(new Phrase(userModel.LastName, docFont))
            {
                RunDirection = PdfWriter.RUN_DIRECTION_RTL,
                HorizontalAlignment = Element.ALIGN_LEFT,
                Border = 0
            });

            infoTable.AddCell(new PdfPCell(new Phrase("شماره\nدانشجویی:", docFont))
            {
                RunDirection = PdfWriter.RUN_DIRECTION_RTL,
                HorizontalAlignment = Element.ALIGN_LEFT,
                Border = 0,
                PaddingBottom = 15
            });

            infoTable.AddCell(new PdfPCell(new Phrase(userModel.StudentNumber, docFont))
            {
                RunDirection = PdfWriter.RUN_DIRECTION_RTL,
                HorizontalAlignment = Element.ALIGN_LEFT,
                Border = 0
            });

            infoTable.AddCell(new PdfPCell(new Phrase("کد ملی:", docFont))
            {
                RunDirection = PdfWriter.RUN_DIRECTION_RTL,
                HorizontalAlignment = Element.ALIGN_LEFT,
                Border = 0
            });

            infoTable.AddCell(new PdfPCell(new Phrase(userModel.NationalCode, docFont))
            {
                RunDirection = PdfWriter.RUN_DIRECTION_RTL,
                HorizontalAlignment = Element.ALIGN_LEFT,
                Border = 0
            });

            infoTable.AddCell(new PdfPCell(new Phrase("واحد دانشگاهی:", docFont))
            {
                RunDirection = PdfWriter.RUN_DIRECTION_RTL,
                HorizontalAlignment = Element.ALIGN_LEFT,
                Border = 0
            });

            infoTable.AddCell(new PdfPCell(new Phrase(userModel.UniversityName, docFont))
            {
                RunDirection = PdfWriter.RUN_DIRECTION_RTL,
                HorizontalAlignment = Element.ALIGN_LEFT,
                Border = 0
            });


            // دو سلول بعدی صرفا جهت تکمیل شدن یک ردیف است تا عملکرد صحیح خود را داشته باشد
            infoTable.AddCell(new PdfPCell(new Phrase("", docFont))
            {
                RunDirection = PdfWriter.RUN_DIRECTION_RTL,
                HorizontalAlignment = Element.ALIGN_LEFT,
                Border = 0
            });

            infoTable.AddCell(new PdfPCell(new Phrase("", docFont))
            {
                RunDirection = PdfWriter.RUN_DIRECTION_RTL,
                HorizontalAlignment = Element.ALIGN_LEFT,
                Border = 0
            });


            int[] infoTableColumnsWidth = { 20, 15, 20, 15 };

            infoTable.SetWidths(infoTableColumnsWidth);

            doc.Add(infoTable);

            doc.Close();
        }

        private static Font GetFont()
        {
            const string fontName = "Iranian Sans";

            if (FontFactory.IsRegistered(fontName))
                return FontFactory.GetFont(fontName, BaseFont.IDENTITY_H, BaseFont.EMBEDDED);

            var fontPath = "Fonts/irsans.ttf"; // مسیر فونت

            FontFactory.Register(fontPath);

            return FontFactory.GetFont(fontName, BaseFont.IDENTITY_H, BaseFont.EMBEDDED);
        }

    }

}
نکته: حتما به تعریف فونت در پوشه‌ی Fonts و عکس‌ها در پوشه Images توجه فرمایید.

و در انتها نحوه‌ی استفاده از کلاس CardReport در یک برنامه‌ی Console: 
    class Program
    {
        static void Main(string[] args)
        {
            var userModel = new UserModel
            {
                FirstName = "علی",
                LastName = "احمدی",
                NationalCode = "1234567890",
                StudentNumber = "23242342",
                UniversityName = "آزاد",
                ImagePath = "Images/avatar.jpg"
            };

            CardReport.Generate(userModel, "Images/competition_logo.jpg", "Images/university_logo.png");

            System.Diagnostics.Process.Start("card.pdf");

        }
    }
     
مطالب
پیاده سازی Row Level Security در Entity framework
در این مقاله قصد داریم به صورت عملی row level security را در زبان #C و Entity framework پیاده سازی نماییم. اینکار باعث خواهد شد، پروژه refactoring آسان‌تری داشته باشد، همچنین باعث کاهش کد‌ها در سمت لایه business می‌گردد و یا اگر از DDD استفاده میکنید، در سرویس‌های خود به صورت چشم گیری کد‌های کمتر و واضح‌تری خواهید داشت. در مجموع راه حل‌های متنوعی برای پیاده سازی این روش ارائه شده است که یکی از آسان‌‌ترین روش‌های ممکن برای انجام اینکار استفاده‌ی درست از interface‌ها و همچنین بحث validation آن در سمت Generic repository میباشد.
فرض کنید در سمت مدل‌های خود User و Post را داشته باشیم و بخواهیم بصورت اتوماتیک رکورد‌های Post مربوط به هر User بارگذاری شود بطوریکه دیگر احتیاجی به نوشتن شرط‌های تکراری نباشد و در صورتیکه آن User از نوع Admin بود، همه‌ی Postها را بتواند ببیند. برای اینکار یک پروژه‌ی Console Application را در Visual studio به نام "Console1" ساخته و بصورت زیر عمل خواهیم کرد:

ابتدا لازم است Entity framework را توسط Nuget packages manager دانلود نمایید. سپس به پروژه‌ی خود یک فولدر جدید را به نام Models و درون آن ابتدا یک کلاس را به نام User اضافه کرده و بدین صورت خواهیم داشت :
using System;

namespace Console1.Models
{
    public enum UserType
    {
        Admin,
        Ordinary
    }
    public class User
    {
        public int Id { get; set; }

        public string Name { get; set; }

        public int Age { get; set; }

        public UserType Type { get; set; }

    }
}  

UserType نیز کاملا مشخص است؛ هر User نقش Admin یا Ordinary را می‌تواند داشته باشد.

نوبت به نوشتن اینترفیس IUser میرسد. در همین پوشه‌ای که قرار داریم، آن را پیاده سازی مینماییم:

namespace Console1.Models
{
    public interface IUser
    {
        int UserId { get; set; }

        User User { get; set; }
    }
}

هر entity که با User ارتباط دارد، باید اینترفیس فوق را پیاده سازی نماید. حال یک کلاس دیگر را به نام Post در همین پوشه درست کرده و بدین صورت پیاده سازی مینماییم.

using System.ComponentModel.DataAnnotations.Schema;

namespace Console1.Models
{
    public class Post : IUser
    {
        public int Id { get; set; }

        public string Context { get; set; }

        public int UserId { get; set; }

        [ForeignKey(nameof(UserId))]
        public User User { get; set; }

    }
}

واضح است که relation از نوع one to many برقرار است و هر User میتواند n تا Post داشته باشد.

خوب تا اینجا کافیست و میخواهیم مدل‌های خود را با استفاده از EF به Context معرفی کنیم. میتوانیم در همین پوشه کلاسی را به نام Context ساخته و بصورت زیر بنویسیم

using System.Data.Entity;

namespace Console1.Models
{
    public class Context : DbContext
    {
        public Context() : base("Context")
        {
        }

        public DbSet<User> Users { get; set; }

        public DbSet<Post> Posts { get; set; }
    }
}

در اینجا مشخص کرده‌ایم که دو Dbset از نوع User و Post را داریم. بدین معنا که EF دو table را برای ما تولید خواهد کرد. همچنین نام کلید رشته‌ی اتصالی به دیتابیس خود را نیز، Context معرفی کرده‌ایم.

خوب تا اینجا قسمت اول پروژه‌ی خود را تکمیل کرده‌ایم. الان میتوانیم با استفاده از Migration دیتابیس خود را ساخته و همچنین رکوردهایی را بدان اضافه کنیم. در Package Manager Console خود دستور زیر را وارد نمایید:

enable-migrations

به صورت خودکار پوشه‌ای به نام Migrations ساخته شده و درون آن Configuration.cs قرار می‌گیرد که آن را بدین صورت تغییر میدهیم:

namespace Console1.Migrations
{
    using Models;
    using System.Data.Entity.Migrations;

    internal sealed class Configuration : DbMigrationsConfiguration<Console1.Models.Context>
    {
        public Configuration()
        {
            AutomaticMigrationsEnabled = true;
        }

        protected override void Seed(Console1.Models.Context context)
        {

            context.Users.AddOrUpdate(x => x.Id,
              new User { Id = 1, Name = "aaa", Age = 30, Type = UserType.Admin },
              new User { Id = 2, Name = "bbb", Age = 20, Type = UserType.Ordinary },
              new User { Id = 3, Name = "ccc", Age = 25, Type = UserType.Ordinary }
            );

            context.Posts.AddOrUpdate(x => x.Id,
                new Post { Context = "ccc 1", UserId = 3 },
                new Post { Context = "bbb 1", UserId = 2 },
                new Post { Context = "bbb 2", UserId = 2 },
                new Post { Context = "aaa 1", UserId = 1 },
                new Post { Context = "bbb 3", UserId = 2 },
                new Post { Context = "ccc 2", UserId = 3 },
                new Post { Context = "ccc 3", UserId = 3 }
            );

            context.SaveChanges();
        }
    }
}

در متد seed، رکورد‌های اولیه را به شکل فوق وارد کرده ایم (رکورد‌ها فقط به منظور تست میباشند*). در کنسول دستور Update-database را ارسال کرده، دیتابیس تولید خواهد شد.

قطعا مراحل بالا کاملا بدیهی بوده و نوشتن آنها بدین دلیل بوده که در Repository که الان میخواهیم شروع به نوشتنش کنیم به مدل‌های فوق نیاز داریم تا بصورت کاملا عملی با مراحل کار آشنا شویم.


حال میخواهیم به پیاده سازی بخش اصلی این مقاله یعنی repository که از Row Level Security پشتیبانی میکند بپردازیم. در ریشه‌ی پروژه‌ی خود پوشه‌ای را به نام Repository ساخته و درون آن کلاسی را به نام GenericRepository میسازیم. پروژه‌ی شما هم اکنون باید ساختاری شبیه به این را داشته باشد.

GenericRepository.cs را اینگونه پیاده سازی مینماییم

using Console1.Models;
using System;
using System.Linq;
using System.Linq.Dynamic;
using System.Linq.Expressions;

namespace Console1.Repository
{
    public interface IGenericRepository<T>
    {
        IQueryable<T> CustomizeGet(Expression<Func<T, bool>> predicate);
        void Add(T entity);
        IQueryable<T> GetAll();
    }

    public class GenericRepository<TEntity, DbContext> : IGenericRepository<TEntity>
        where TEntity : class, new() where DbContext : Models.Context, new()
    {

        private DbContext _entities = new DbContext();

        public IQueryable<TEntity> CustomizeGet(Expression<Func<TEntity, bool>> predicate)
        {
            IQueryable<TEntity> query = _entities.Set<TEntity>().Where(predicate);
            return query;
        }

        public void Add(TEntity entity)
        {
            int userId = Program.UserId; // یوزد آی دی بصورت فیک ساخته شده
                            // اگر از آیدنتیتی استفاده میکنید میتوان آی دی و هر چیز دیگری که کلیم شده را در اختیار گرفت

            if (typeof(IUser).IsAssignableFrom(typeof(TEntity)))
            {
                ((IUser)entity).UserId = userId;
            }

            _entities.Set<TEntity>().Add(entity);
        }

        public IQueryable<TEntity> GetAll()
        {
            IQueryable<TEntity> result = _entities.Set<TEntity>();

            int userId = Program.UserId; // یوزد آی دی بصورت فیک ساخته شده
                            // اگر از آیدنتیتی استفاده میکنید میتوان آی دی و هر چیز دیگری که کلیم شده را در اختیار گرفت

            if (typeof(IUser).IsAssignableFrom(typeof(TEntity)))
            {
                User me = _entities.Users.Single(c => c.Id == userId);
                if (me.Type == UserType.Admin)
                {
                    return result;
                }
                else if (me.Type == UserType.Ordinary)
                {
                    string query = $"{nameof(IUser.UserId).ToString()}={userId}";
                    
                    return result.Where(query);
                }
            }
            return result;
        }
        public void Commit()
        {
            _entities.SaveChanges();
        }
    }
}
توضیح کد‌های فوق

1) یک اینترفیس Generic را به نام IGenericRepository داریم که کلاس GenericRepository قرار است آن را پیاده سازی نماید.

2) این اینترفیس شامل متدهای CustomizeGet است که فقط یک predicate را گرفته و خیلی مربوط به این مقاله نیست (صرفا جهت اطلاع). اما متد Add و GetAll بصورت مستقیم قرار است هدف row level security را برای ما انجام دهند.

3) کلاس GenericRepository دو Type عمومی را به نام TEntity و DbContext گرفته و اینترفیس IGenericRepository را پیاده سازی مینماید. همچنین صریحا اعلام کرده‌ایم TEntity از نوع کلاس و DbContext از نوع Context ایی است که قبلا نوشته‌ایم.

4) پیاده سازی متد CustomizeGet را مشاهده مینمایید که کوئری مربوطه را ساخته و بر میگرداند.

5) پیاده سازی متد Add بدین صورت است که به عنوان پارامتر، TEntity را گرفته (مدلی که قرار است save شود). بعد مشاهد میکنید که من به صورت hard code به UserId مقدار داده‌ام. قطعا میدانید که برای این کار به فرض اینکه از Asp.net Identity استفاده میکنید، میتوانید Claim آن Id کاربر Authenticate شده را بازگردانید.

با استفاده از IsAssignableFrom مشخص کرده‌ایم که آیا TEntity یک Typeی از IUser را داشته است یا خیر؟ در صورت true بودن شرط، UserId را به TEntity اضافه کرده و بطور مثال در Service‌های خود نیازی به اضافه کردن متوالی این فیلد نخواهید داشت و در مرحله‌ی بعد نیز آن را به entity_ اضافه مینماییم.

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

6) به جالبترین متد که GetAll میباشد میرسیم. ابتدا کوئری را از آن Entity ساخته و در مرحله‌ی بعد مشخص مینماییم که آیا TEntity یک Typeی از IUser میباشد یا خیر؟ در صورت برقرار بودن شرط، User مورد نظر را یافته در صورتیکه Typeی از نوع Admin داشت، همه‌ی مجموعه را برخواهیم گرداند (Admin میتواند همه‌ی پست‌ها را مشاهده نماید) و در صورتیکه از نوع Ordinary باشد، با استفاده از dynamic linq، کوئری مورد نظر را ساخته و شرط را ایجاد می‌کنیم که UserId برابر userId مورد نظر باشد. در این صورت بطور مثال همه‌ی پست‌هایی که فقط مربوط به user خودش میباشد، برگشت داده میشود.

نکته: برای دانلود dynamic linq کافیست از طریق nuget آن را جست و جو نمایید: System.Linq.Dynamic

و اگر هم از نوع IUser نبود، result را بر میگردانیم. بطور مثال فرض کنید مدلی داریم که قرار نیست security روی آن اعمال شود. پس کوئری ساخته شده قابلیت برگرداندن همه‌ی رکورد‌ها را دارا میباشد. 

7) متد Commit هم که پرواضح است عملیات save را اعمال میکند.


قبلا در قسمت Seed رکوردهایی را ساخته بودیم. حال میخواهیم کل این فرآیند را اجرا نماییم. Program.cs را از ریشه‌ی پروژه‌ی خود باز کرده و اینگونه تغییر میدهیم:

using System;
using Console1.Models;
using Console1.Repository;
using System.Collections.Generic;
using System.Linq;

namespace Console1
{
    public class Program
    {
        public static int UserId = 1; //fake userId
        static void Main()
        {
            GenericRepository<Post, Context> repo = new GenericRepository<Post, Context>();

            List<Post> posts = repo.GetAll().ToList();

            foreach (Post item in posts)
                Console.WriteLine(item.Context);

            Console.ReadKey();
        }
    }
}

همانطور که ملاحظه می‌کنید، UserId به صورت fake ساخته شده است. آن چیزی که هم اکنون در دیتابیس رفته، بدین صورت است که UserId = 1 برابر Admin و بقیه Ordinary میباشند. در متد Main برنامه، یک instance از GenericRepository را گرفته و بعد با استفاده از متد GetAll و لیست کردن آن، همه‌ی رکورد‌های مورد نظر را برگردانده و سپس چاپ مینماییم. در صورتی که UserId برابر 1 باشد، توقع داریم که همه‌ی رکورد‌ها بازگردانده شود:

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

میبینید که تنها با تغییر userId رفتار عوض شده و فقط Postهای مربوط به آن User خاص برگشت داده میشود.


از همین روش میتوان برای طراحی Repositoryهای بسیار پیچیده‌تر نیز استفاده کرد و مقدار زیادی از validationها را به طور مستقیم بدان واگذار نمود!

دانلود کد‌ها در Github

مطالب
بررسی نحوه‌ی راه اندازی پروژه‌ی Decision
پروژه‌ی Decision را می‌توان چکیده‌ی تمام مطالب سایت دانست که در آن جمع آوری نکات ASP.NET MVC 5.x، EF Code First 6.x، مباحث تزریق وابستگی‌ها، کار با AutoMapper، بوت استرپ 3 و غیره لحاظ شده‌اند. به همین جهت درک آن بدون مطالعه‌ی « تمام » مطالب سایت میسر نیست و همچنین راه اندازی آن.
در این مطلب با توجه به سؤالات زیادی که در مورد صرفا نحوه‌ی اجرای بدون خطای آن وجود داشت، ریز مراحل آن‌را بررسی می‌کنیم.


پیشنیازهای توسعه‌ی برنامه
- با توجه به استفاده از ویژگی‌های C# 6 در این پروژه، حتما نیاز است برای کار و اجرای آن از VS 2015 استفاده کنید.
- همچنین این پروژه از قابلیت «فایل استریم» SQL Server استفاده می‌کند. بنابراین نیاز است نگارش متناسبی از SQL Server را پیشتر نصب کرده باشید (هر نگارشی بالاتر از SQL Server 2005).
- اگر از ReSharper استفاده می‌کنید، به صورت موقت آن‌را به حالت تعلیق درآورید (منوی tools، گرینه‌ی options و انتخاب resharper و سپس suspend کردن آن). این مورد سرعت بازیابی بسته‌های نیوگت را به شدت افزایش می‌دهد.


بازیابی وابستگی‌های نیوگت پروژه

مرسوم نیست چند 10 مگابایت وابستگی‌های پروژه را به صورت فایل‌های باینری، به مخزن کدها ارسال کرد. از این جهت که نیوگت بر اساس مداخل فایل‌های packages.config، قابلیت بازیابی و نصب خودکار آن‌ها را دارد. بنابراین ابتدا package manger console را باز کنید؛ از طریق منوی Tools -> NuGet Package Manager -> Package Manager Console


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

پس از پایان کار بازیابی بسته‌ها، یکبار کل Solution را Build کنید تا مطمئن شوید که تمام بسته‌های مورد نیاز به درستی بازیابی و نصب شده‌اند (Ctrl+Shift+B و یا همان منوی Build و انتخاب گزینه‌ی Build Solution).



تنظیمات رشته اتصالی بانک اطلاعاتی برنامه

پس از Build موفق کل Solution در مرحله‌ی قبل، اکنون نوبت به برپایی تنظیمات بانک اطلاعاتی برنامه است. برای این منظور فایل web.config ذیل را باز کنید:
Decision\src\Decision.Web\Web.config
یک چنین تنظیمی را مشاهده می‌کنید:
  <connectionStrings>
    <clear />
    <add name="DefaultConnection" connectionString="Data Source=.\sqlexpress;Initial Catalog=DecisionDb;Integrated Security = true;MultipleActiveResultSets=True;" providerName="System.Data.SqlClient" />
  </connectionStrings>
از آنجائیکه بر روی سیستم من SQL Server نگارش Developer نصب است و از SQL Server Express استفاده نمی‌کنم، تنظیمات فوق را به نحو ذیل تغییر خواهم داد:
  <connectionStrings>
    <clear />
    <add name="DefaultConnection" connectionString="Data Source=(local);Initial Catalog=DecisionDb;Integrated Security = true;MultipleActiveResultSets=True;" providerName="System.Data.SqlClient" />
  </connectionStrings>
تنها تغییر صورت گرفته، تنظیم data source است. مابقی موارد یکی است و تفاوتی نمی‌کند.

در این حالت نیاز است بانک اطلاعاتی خالی DecisionDb را خودتان ایجاد کنید. علت آن به AutomaticMigrationsEnabled = false بر می‌گردد؛ که در ادامه توضیح داده شده‌است و همچنین وجود تنظیم ذیل در فایل Decision\src\Decision.Web\App_Start\ApplicationStart.cs
 Database.SetInitializer<ApplicationDbContext>(null);
این تنظیم و نال بودن پارامتر ورودی آن به این معنا است که اولا برنامه یک بانک اطلاعاتی جدید را به صورت خودکار ایجاد نمی‌کند و همچنین کار Migrations خودکار نیست.


ایجاد بانک اطلاعاتی برنامه و تنظیمات آن

پس از آن، نوبت به ایجاد بانک اطلاعاتی برنامه است. چون این برنامه از EF Code first استفاده می‌کند، قادر است بانک اطلاعاتی ذکر شده‌ی در Initial Catalog فوق را به صورت خودکار ایجاد کند (با تمام جداول، روابط و تنظیمات آن‌ها). این اطلاعات هم از پروژه‌ی Decision.DataLayer و پوشه‌ی Migrations آن تامین می‌شوند.
اگر به فایل Decision\src\Decision.DataLayer\Migrations\201602072159421_Initial.cs مراجعه کنید، یکسری تنظیمات دستی را هم علاوه بر کدهای خودکار EF، مشاهده خواهید کرد:
 //. . .
Sql("EXEC sp_configure filestream_access_level, 2");
Sql("RECONFIGURE", true);

Sql("alter database DecisionDb Add FileGroup FileGroupApplicant contains FileStream", true);
Sql("alter database DecisionDb add file ( name = 'ApplicantDocuements'  ,  filename = 'C:\\FileStream\\ApplicantDocuements') to filegroup FileGroupApplicant", true);
//. . .
این‌ها مواردی هستند که کار تنظیمات فایل استریم را به صورت خودکار انجام می‌دهند.
بنابراین نیاز است در درایور C، پوشه‌ی خالی FileStream از پیش تهیه شده باشد (نیازی به ایجاد پوشه‌ی ApplicantDocuements نیست و این پوشه به صورت خودکار ایجاد می‌شود).

و در فایل Decision\src\Decision.DataLayer\Migrations\Configuration.cs مشخص شده‌است که AutomaticMigrationsEnabled = false. به این معنا که تنظیمات فوق به صورت خودکار به بانک اطلاعاتی اعمال نشده و باید چند دستور ذیل را به صورت دستی صادر کنیم:
الف) ابتدا package manager console را مجددا باز کنید و در اینجا default project را بر روی Decision.DataLayer قرار دهید. از این جهت که قرار است اطلاعات migration را از این پروژه دریافت کنیم:


در غیراینصورت پیام خطای No migrations configuration type was found in the assembly را دریافت خواهید کرد.

ب) سپس دستور ذیل را صادر کنید (با این فرض که بانک اطلاعاتی خالی DecisionDb ذکر شده‌ی در قسمت قبل را پیشتر ایجاد کرده‌اید):
 PM> Update-Database -Verbose -ConnectionStringName "DefaultConnection" -StartUpProjectName "Decision.Web"
این تنظیمات به این معنا است که Update-Database را بر اساس اطلاعات پروژه‌ی Decision.DataLayer انجام بده (همان انتخاب default project)؛ اما رشته‌ی اتصالی را از پروژه‌ی Decision.Web و تنظیمات DefaultConnection آن دریافت کن.

من در این حالت پیام خطای Update-Database : The term 'Update-Database' is not recognized as the name of a cmdlet را دریافت کردم.
راه حل: یکبار ویژوال استودیو را بسته و مجددا باز کنید تا کار نصب بسته‌ها و بارگذاری تمام وابستگی‌های آن‌ها به درستی صورت گیرد. این خطا به این معنا است که هرچند NuGet کار نصب EF را انجام داده‌است، اما هنوز اسکریپت‌های پاورشل آن که دستوراتی مانند Update-Database را اجرا می‌کنند، بارگذاری نشده‌اند. راه حل آن بستن و اجرای مجدد ویژوال استودیو است.
پس از اجرای مجدد ویژوال استودیو و انتخاب default project صحیح (مطابق تصویر فوق)، مجددا دستور Update-Database  فوق را صادر کنید (با پارامترهای ویژه‌ی آن).
با صدور این دستور، پیام خطای ذیل را دریافت کردم:
 The Entity Framework provider type 'System.Data.Entity.SqlServer.SqlProviderServices, EntityFramework'
registered in the application config file for the ADO.NET provider with invariant name 'System.Data.SqlClient' could not be loaded.
برای رفع آن نیاز است EF را یکبار دیگر نصب کنید:
 PM> Update-Package -Reinstall "EntityFramework" -ProjectName "Decision.DataLayer"
در ادامه مجددا کل Solution را Build کنید؛ چون Migrations بر اساس اطلاعات اسمبلی‌های کامپایل شده‌ی پروژه کار می‌کند.
اینبار دستور update-database فوق (با پارامترهای ویژه‌ی آن) بدون مشکل اجرا شد و بانک اطلاعاتی مربوطه تشکیل گردید.




اکنون برنامه قابل اجرا است و در این حالت است که می‌توان دکمه‌ی F5 را جهت اجرای برنامه فشرد. البته در این حالت بر روی پروژه‌ی Decision.Web کلیک راست کرده و گزینه‌ی set as startup project را نیز انتخاب کنید و سپس F5:



لطفا سؤالاتی را که مرتبط با «راه اندازی» این پروژه نیستند، در قسمت بازخوردهای اختصاصی آن مطرح کنید.
اشتراک‌ها
کتابخانه alloy-ui

AlloyUI is a framework built on top of YUI3 (JavaScript) that uses Bootstrap 3 (HTML/CSS) to provide a simple API for building high scalable applications  Demo

کتابخانه alloy-ui