مطالب دوره‌ها
استفاده از StructureMap به عنوان یک IoC Container
StructureMap یکی از IoC containerهای بسیار غنی سورس باز نوشته شده برای دات نت فریم ورک است. امکان تنظیمات آن توسط کدنویسی و یا همان Fluent interfaces، به کمک فایل‌های کانفیگ XML و همچنین استفاده از ویژگی‌ها یا Attributes نیز میسر است. امکانات جانبی دیگری را نیز مانند یکی شدن با فریم ورک‌های Dynamic Proxy برای ساده سازی فرآیندهای برنامه نویسی جنبه‌گرا یا AOP، دارا است. در ادامه قصد داریم با نحوه استفاده از این فریم ورک IoC بیشتر آشنا شویم.


دریافت StructureMap

برای دریافت آن نیاز است دستور پاورشل ذیل را در کنسول نیوگت ویژوال استودیو فراخوانی کنید:
 PM> Install-Package structuremap
البته باید دقت داشت که برای استفاده از StructureMap نیاز است به خواص پروژه مراجعه و سپس حالت Client profile را به Full profile تغییر داد تا برنامه قابل کامپایل باشد (در برنامه‌های دسکتاپ البته)؛ از این جهت که StructureMap ارجاعی را به اسمبلی استاندارد System.Web دارد.


آشنایی با ساختار برنامه

ابتدا یک برنامه کنسول را آغاز کرده و سپس یک Class library جدید را به نام Services نیز به آن اضافه کنید. در ادامه کلاس‌ها و اینترفیس‌های زیر را به Class library ایجاد شده، اضافه کنید. سپس از طریق نیوگت به روشی که گفته شد، StructureMap را به پروژه اصلی (ونه پروژه Class library) اضافه نمائید و Target framework آن‌را نیز در حالت Full قرار دهید بجای حالت Client profile.
namespace DI03.Services
{
    public interface IUsersService
    {
        string GetUserEmail(int userId);
    }
}


namespace DI03.Services
{
    public interface IEmailsService
    {
        void SendEmailToUser(int userId, string subject, string body);
    }
}

using System;

namespace DI03.Services
{
    public class UsersService : IUsersService
    {
        public UsersService()
        {
            //هدف صرفا نمایش وهله سازی خودکار این وابستگی است
            Console.WriteLine("UsersService ctor.");
        }

        public string GetUserEmail(int userId)
        {
            //برای مثال دریافت از بانک اطلاعاتی و بازگشت یک نمونه جهت آزمایش برنامه
            return "name@site.com";
        }
    }
}

using System;

namespace DI03.Services
{
    public class EmailsService: IEmailsService
    {
        private readonly IUsersService _usersService;
        public EmailsService(IUsersService usersService)
        {
            Console.WriteLine("EmailsService ctor.");
            _usersService = usersService;
        }

        public void SendEmailToUser(int userId, string subject, string body)
        {
            var email = _usersService.GetUserEmail(userId);
            Console.WriteLine("SendEmailTo({0})", email);
        }
    }
}
در لایه سرویس برنامه، یک سرویس کاربران و یک سرویس ارسال ایمیل تدارک دیده شده‌اند.
سرویس کاربران بر اساس آی دی یک کاربر، برای مثال از بانک اطلاعاتی ایمیل او را بازگشت می‌دهد. سرویس ارسال ایمیل، نیاز به ایمیل کاربری برای ارسال ایمیلی به او دارد. بنابراین وابستگی مورد نیاز خود را از طریق تزریق وابستگی‌ها در سازنده کلاس و وهله سازی شده در خارج از آن (معکوس سازی کنترل)، دریافت می‌کند.
در سازنده‌های هر دو کلاس سرویس نیز از Console.WriteLine استفاده شده‌است تا زمان وهله سازی خودکار آن‌ها را بتوان بهتر مشاهده کرد.
نکته مهمی که در اینجا وجود دارد، بی‌خبری لایه سرویس از وجود IoC Container مورد استفاده است.


استفاده از لایه سرویس و تزریق وابستگی‌ها به کمک  StructureMap

using DI03.Services;
using StructureMap;

namespace DI03
{
    class Program
    {
        static void Main(string[] args)
        {
            // تنظیمات اولیه برنامه که فقط یکبار باید در طول عمر برنامه انجام شود
            ObjectFactory.Initialize(x =>
            {
                x.For<IEmailsService>().Use<EmailsService>();
                x.For<IUsersService>().Use<UsersService>();
            });

            //نمونه‌ای از نحوه استفاده از تزریق وابستگی‌های خودکار
            var emailsService = ObjectFactory.GetInstance<IEmailsService>();
            emailsService.SendEmailToUser(userId: 1, subject: "Test", body: "Hello!");
        }
    }
}
کدهای برنامه را به نحو فوق تغییر دهید. در ابتدا نحوه سیم کشی‌های آغازین برنامه را مشاهده می‌کنید. برای مثال کدهای ObjectFactory.Initialize باید در متدهای آغازین یک پروژه قرار گیرند و تنها یکبار هم نیاز است فراخوانی شوند.
به این ترتیب IoC Container ما زمانیکه قرار است object graph مربوط به IEmailsService درخواستی را تشکیل دهد، خواهد دانست ابتدا به سازنده‌ی کلاس EmailsService می‌رسد. در اینجا برای وهله سازی این کلاس به صورت خودکار، باید وابستگی‌های آن‌را نیز وهله سازی کند. بنابراین بر اساس تنظیمات آغازین برنامه می‌داند که باید از کلاس UsersService برای تزریق خودکار وابستگی‌ها در سازنده کلاس ارسال ایمیل استفاده نماید.
در این حالت اگر برنامه را اجرا کنیم، به خروجی زیر خواهیم رسید:
UsersService ctor.
EmailsService ctor.
SendEmailTo(name@site.com)
بنابراین در اینجا با مفهوم Object graph نیز آشنا شدیم. فقط کافی است وابستگی‌ها را در سازنده‌های کلاس‌ها تعریف کرده و سیم کشی‌های آغازین صحیحی را نیز در ابتدای برنامه معرفی نمائیم. کار وهله سازی چندین سطح با تمام وابستگی‌های متناظر با آن‌ها در اینجا به صورت خودکار انجام خواهد شد و نهایتا یک شیء قابل استفاده بازگشت داده می‌شود.
ابتدایی‌ترین مزیت استفاده از تزریق وابستگی‌ها امکان تعویض آن‌ها است؛ خصوصا در حین Unit testing. اگر کلاسی برای مثال قرار است با شبکه کار کند، می‌توان پیاده سازی آن‌را با یک نمونه اصطلاحا Fake جایگزین کرد و در این نمونه تنها نتیجه‌ی کار را بازگشت داد. کلاس‌های لایه سرویس ما تنها با اینترفیس‌ها کار می‌کنند. این تنظیمات قابل تغییر اولیه IoC container مورد استفاده هستند که مشخص می‌کنند چه کلاس‌هایی باید در سازنده‌های کلاس‌ها تزریق شوند.


تعیین طول عمر اشیاء در StructureMap

برای اینکه بتوان طول عمر اشیاء را بهتر توضیح داد، کلاس سرویس کاربران را به نحو زیر تغییر دهید:
using System;

namespace DI03.Services
{
    public class UsersService : IUsersService
    {
        private int _i;
        public UsersService()
        {
            //هدف صرفا نمایش وهله سازی خودکار این وابستگی است
            Console.WriteLine("UsersService ctor.");
        }

        public string GetUserEmail(int userId)
        {
            _i++;
            Console.WriteLine("i:{0}", _i);
            //برای مثال دریافت از بانک اطلاعاتی و بازگشت یک نمونه جهت آزمایش برنامه
            return "name@site.com";
        }
    }
}
به عبارتی می‌خواهیم بدانیم این کلاس چه زمانی وهله سازی مجدد می‌شود. آیا در حالت فراخوانی ذیل،
 //نمونه‌ای از نحوه استفاده از تزریق وابستگی‌های خودکار
var emailsService1 = ObjectFactory.GetInstance<IEmailsService>();
emailsService1.SendEmailToUser(userId: 1, subject: "Test1", body: "Hello!");

var emailsService2 = ObjectFactory.GetInstance<IEmailsService>();
emailsService2.SendEmailToUser(userId: 1, subject: "Test2", body: "Hello!");
ما شاهد چاپ عدد 2 خواهیم بود یا عدد یک:
 UsersService ctor.
EmailsService ctor.
i:1
SendEmailTo(name@site.com)
UsersService ctor.
EmailsService ctor.
i:1
SendEmailTo(name@site.com)
همانطور که ملاحظه می‌کنید، به ازای هربار فراخوانی ObjectFactory.GetInstance، یک وهله جدید ایجاد شده است. بنابراین مقدار i در هر دو بار مساوی عدد یک است.
اگر به هر دلیلی نیاز بود تا این رویه تغییر کند، می‌توان بر روی طول عمر اشیاء تشکیل شده نیز تاثیر گذار بود. برای مثال تنظیمات آغازین برنامه را به نحو ذیل تغییر دهید:
// تنظیمات اولیه برنامه که فقط یکبار باید در طول عمر برنامه انجام شود
ObjectFactory.Initialize(x =>
{
   x.For<IEmailsService>().Use<EmailsService>();
   x.For<IUsersService>().Singleton().Use<UsersService>();
});
اینبار اگر برنامه را اجرا کنیم، به خروجی ذیل خواهیم رسید:
 UsersService ctor.
EmailsService ctor.
i:1
SendEmailTo(name@site.com)
EmailsService ctor.
i:2
SendEmailTo(name@site.com)
بله. با Singleton معرفی کردن تنظیمات UsersService، تنها یک وهله از این کلاس ایجاد خواهد شد و نهایتا در فراخوانی دوم ObjectFactory.GetInstance، شاهد عدد i مساوی 2 خواهیم بود (چون از یک وهله استفاده شده است).

حالت‌های دیگر تعیین طول عمر مطابق متدهای زیر هستند:
 Singleton()
HttpContextScoped()
HybridHttpOrThreadLocalScoped()
با انتخاب حالت HttpContext، به ازای هر HttpContext ایجاد شده، کلاس معرفی شده یکبار وهله سازی می‌گردد.
در حالت ThreadLocal، به ازای هر Thread، وهله‌ای متفاوت در اختیار مصرف کننده قرار می‌گیرد.
حالت Hybrid ترکیبی است از حالت‌های HttpContext و ThreadLocal. اگر برنامه وب بود، از HttpContext استفاده خواهد کرد در غیراینصورت به ThreadLocal سوئیچ می‌کند.

شاید بپرسید که کاربرد مثلا HttpContextScoped در کجا است؟
در یک برنامه وب نیاز است تا یک وهله از DbContext (مثلا Entity framework) را در اختیار کلاس‌های مختلف لایه سرویس قرار داد. به این ترتیب چون هربار new Context صورت نمی‌گیرد، هربار هم اتصال جداگانه‌ای به بانک اطلاعاتی باز نخواهد شد. نتیجه آن رسیدن به یک برنامه سریع، با سربار کم و همچنین کار کردن در یک تراکنش واحد است. چون هربار فراخوانی new Context به معنای ایجاد یک تراکنش جدید است.
همچنین در این برنامه وب قصد نداریم از حالت طول عمر Singleton استفاده کنیم، چون در این حالت یک وهله از Context در اختیار تمام کاربران سایت قرار خواهد گرفت (و DbContext به صورت Thread safe طراحی نشده است). نیاز است به ازای هر کاربر و به ازای طول عمر هر درخواست، تنها یکبار این وهله سازی صورت گیرد. بنابراین در این حالت استفاده از HttpContextScoped توصیه می‌شود. به این ترتیب در طول عمر کوتاه Object graph‌های تشکیل شده، فقط یک وهله از DbContext ایجاد و استفاده خواهد شد که بسیار مقرون به صرفه است.
مزیت دیگر مشخص سازی طول عمر به نحو HttpContextScoped، امکان Dispose خودکار آن به صورت زیر است:
protected void Application_EndRequest(object sender, EventArgs e)  
{  
  ObjectFactory.ReleaseAndDisposeAllHttpScopedObjects();  
}

تنظیمات خودکار اولیه در StructureMap

اگر نام اینترفیس‌های شما فقط یک I در ابتدا بیشتر از نام کلاس‌های متناظر با آن‌ها دارد، مثلا مانند ITest و کلاس Test هستند؛ فقط کافی است از قراردادهای پیش فرض StructureMap برای اسکن یک یا چند اسمبلی استفاده کنیم:
 // تنظیمات اولیه برنامه که فقط یکبار باید در طول عمر برنامه انجام شود
ObjectFactory.Initialize(x =>
{
   //x.For<IEmailsService>().Use<EmailsService>();
   //x.For<IUsersService>().Singleton().Use<UsersService>();  
   x.Scan(scan =>
   {
       scan.AssemblyContainingType<IEmailsService>();
       scan.WithDefaultConventions();
   });  
});
در این حالت دیگر نیازی نیست به ازای اینترفیس‌های مختلف و کلاس‌های مرتبط با آن‌ها، تنظیمات اضافه‌تری را تدارک دید. کار یافتن و برقراری اتصالات لازم در اینجا خودکار خواهد بود.


دریافت مثال قسمت جاری
DI03.zip

به روز شده‌ی این مثال‌ها را بر اساس آخرین تغییرات وابستگی‌های آن‌ها از مخزن کد ذیل می‌توانید دریافت کنید:
Dependency-Injection-Samples
 
مطالب
حذف محدودیت‌های فایل‌های PDF توسط iTextSharp
پیشنیاز
«رمزنگاری فایل‌های PDF با استفاده از کلید عمومی توسط iTextSharp»

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

الف) رمزگشایی با استفاده از کلمه عبور
using System.IO;
using iTextSharp.text.pdf;

namespace PdfDecryptor.Core
{
    public class PasswordDecryptor
    {
        public string ReadPassword { set; get; }
        public string PdfPath { set; get; }
        public string OutputPdf { set; get; }

        public void DecryptPdf()
        {
            PdfReader.unethicalreading = true;

            PdfReader reader;
            if (string.IsNullOrWhiteSpace(ReadPassword))
                reader = new PdfReader(PdfPath);
            else
                reader = new PdfReader(PdfPath, System.Text.Encoding.UTF8.GetBytes(ReadPassword));

            using (var stamper = new PdfStamper(reader, new FileStream(OutputPdf, FileMode.Create)))
            {
                stamper.Close();
            }
        }
    }
}
کلاس فوق دوکاربرد را می‌تواند به همراه داشته باشد:
- اگر PDF ایی صرفا دارای محدودیت چاپ بوده و این قابلیت ویژه آن غیرفعال شده است، فقط کافی است مسیر فایل PDF موجود (PdfPath) و مسیر فایل جدیدی که قرار است تولید شود (OutputPdf) ذکر گردد. خروجی فایلی خواهد بود که هیچگونه محدودیتی ندارد. این مساله هم صرفا توسط PdfReader.unethicalreading میسر شده است. به عبارتی ذکر و تنظیم edit password در فایل‌های PDF فاقد امنیت است. همین اندازه که PdfReader می‌تواند فایلی را بخواند، امکان تهیه یک کپی بدون محدودیت از آن توسط PdfStamper وجود خواهد داشت.
در مورد ReadPassword در پیشنیاز ذکر شده، توضیحات کافی به همراه تصویر وجود دارد؛ حالت خاصی که کاربران برای مشاهده محتویات فایل نیاز خواهند داشت تا کلمه‌ی عبور مرتبط را وارد نمایند. در اینجا ذکر ReadPassword الزامی  است. خروجی نهایی کلاس فوق رفع کامل این محدودیت است.


ب) رمزگشایی توسط کلید عمومی
using System.IO;
using iTextSharp.text.pdf;

namespace PdfDecryptor.Core
{
    public class Decryptor
    {
        public string PfxPath { set; get; }
        public string PfxPassword { set; get; }
        public string InputPdf { set; get; }
        public string OutputPdf { set; get; }

        public void DecryptPdf()
        {
            var certs = new PfxReader().ReadCertificate(PfxPath, PfxPassword);
            var reader = new PdfReader(InputPdf, certs.X509Certificates[0], certs.PrivateKey);
            using (var stamper = new PdfStamper(reader, new FileStream(OutputPdf, FileMode.Create)))
            {
                stamper.Close();
            }
        }
    }
}
در اینجا کدهای کامل رمزگشایی فایل PDF ایی که توسط فایل‌های مخصوص PFX رمزنگاری شده است را مشاهده می‌کنید. کلاس PfxReader آن در پیشنیاز بحث موجود است.
در این حالت مسیر فایل PFX به همراه کلمه عبور آن (PfxPassword) باید مشخص شود. خروجی فایلی است بدون محدودیت خاصی.


پ.ن.
این مثال را به صورت یک فایل اجرایی از اینجا می‌توانید دریافت کنید.
مطالب
ایجاد ایندکس منحصربفرد بر روی چند فیلد با هم در EF Code first
در EF 6 امکان تعریف ساده‌تر ایندکس‌ها توسط data annotations میسر شده‌است. برای مثال:
public abstract class BaseEntity
{
    public int Id { get; set; }
}

public class User : BaseEntity
{
   [Index(IsUnique = true)]
   public string EmailAddress { get; set; }
}
در اینجا توسط ویژگی Index، خاصیت آدرس ایمیل به صورت منحصربفرد تعریف شده‌است.

سؤال: چگونه می‌توان شبیه به composite keys، اما نه دقیقا composite keys، بر روی چند فیلد با هم ایندکس منحصربفرد تعریف کرد؟
    public class UserRating : BaseEntity
    {
        public VoteSectionType SectionType { set; get; }

        public double RatingValue { get; set; }

        public int SectionId { get; set; }

        [ForeignKey("UserId")]
        public virtual User User { set; get; }
        public int UserId { set; get; }
    }
در اینجا جدول رای‌های ثبت شده‌ی یک سیستم را مشاهده می‌کنید. می‌خواهیم یک کاربر نتواند بیش از یک رای به یک مطلب خاص بدهد. به عبارتی نیاز است بر روی SectionType (مطلب، اشتراک‌ها، دوره‌ها و ...)، SectionId (شماره مطلب) و UserId (شماره کاربر) یک کلید منحصربفرد ترکیبی تعریف کرد. ترکیب این سه مورد باید در کل جدول منحصربفرد باشند (Multiple column indexes).
همچنین نمی‌خواهیم Composite key هم تعریف کنیم. می‌خواهیم Id و Primary key این جدول مانند قبل برقرار باشد.
انجام چنین کاری در EF 6.1 به نحو ذیل میسر شده‌است:
    public class UserRating : BaseEntity
    {
        [Index("IX_Single_UserRating", IsUnique = true, Order = 1)] //کلید منحصربفرد ترکیبی روی سه ستون
        public VoteSectionType SectionType { set; get; }

        public double RatingValue { get; set; }

        [Index("IX_Single_UserRating", IsUnique = true, Order = 2)]
        public int SectionId { get; set; }

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

        [Index("IX_Single_UserRating", IsUnique = true, Order = 3)]
        public int? UserId { set; get; }
    }
نکته‌ی انجام اینکار، تعریف Indexها با یک نام یکسان صریحا مشخص شده‌است. در اینجا سه ایندکس تعریف شده‌اند؛ اما نام آن‌ها یکی است و مساوی IX_Single_UserRating قرار داده شده‌است. هر سه مورد نیز IsUnique تعریف شده‌اند و Order آن‌ها نیز باید مشخص گردد.
خروجی SQL چنین تنظیمی به صورت زیر است:
 CREATE UNIQUE INDEX [IX_Single_UserRating]
ON [UserRatings] ([SectionType] ASC,[SectionId] ASC,[UserId] ASC);
 
نظرات مطالب
حذف فضاهای خالی در خروجی صفحات ASP.NET MVC
با تشکر از مطلب ارسالی شما 
برای اینکه فضای خالی به درستی حذف شود و همچنین تگ Pre هم در این الگوریتم لحاظ نشود. می‌توان از اکشن فیلتر زیر استفاده کرد 
public class RemoveWhitespacesAttribute : ActionFilterAttribute
    {

        public override void OnActionExecuted(ActionExecutedContext filterContext)
        {

            var response = filterContext.HttpContext.Response;
      
            if (filterContext.HttpContext.Request.RawUrl != "/sitemap.xml")
            {

                if (response.ContentType == "text/html" && response.Filter != null)
                {
                    response.Filter = new HelperClass(response.Filter);
                }
            }
        }

        private class HelperClass : Stream
        {

            private System.IO.Stream Base;

            public HelperClass(System.IO.Stream ResponseStream)
            {

                if (ResponseStream == null)
                    throw new ArgumentNullException("ResponseStream");
                this.Base = ResponseStream;
            }

            StringBuilder s = new StringBuilder();

            public override void Write(byte[] buffer, int offset, int count)
            {

                string HTML = Encoding.UTF8.GetString(buffer, offset, count);

                Regex reg = new Regex(@"(?<=\s)\s+(?![^<>]*</pre>)");
                HTML = reg.Replace(HTML, string.Empty);

                buffer = System.Text.Encoding.UTF8.GetBytes(HTML);
                this.Base.Write(buffer, 0, buffer.Length);
            }

            #region Other Members

            public override int Read(byte[] buffer, int offset, int count)
            {

                throw new NotSupportedException();
            }

            public override bool CanRead { get { return false; } }

            public override bool CanSeek { get { return false; } }

            public override bool CanWrite { get { return true; } }

            public override long Length { get { throw new NotSupportedException(); } }

            public override long Position
            {

                get { throw new NotSupportedException(); }
                set { throw new NotSupportedException(); }
            }

            public override void Flush()
            {

                Base.Flush();
            }

            public override long Seek(long offset, SeekOrigin origin)
            {

                throw new NotSupportedException();
            }

            public override void SetLength(long value)
            {

                throw new NotSupportedException();
            }

            #endregion
        }

    }
برای اجرا هم در Global.asax آن را فراخوانی کرد.  
 protected void Application_Start()
        {
            try
            {
                GlobalFilters.Filters.Add(new App_Start.RemoveWhitespacesAttribute());
            }
            catch
            {
                HttpRuntime.UnloadAppDomain(); // سبب ری استارت برنامه و آغاز مجدد آن با درخواست بعدی می‌شود
                throw;
            }

        }
در نهایت خروجی به شکل زیر رندر می‌شود

برای Gzip هم  اکثر در این حالت که هردو مورد با هم قرار داده شده است در برخی از موارد فایل‌های جاواسکریپ را با مشکل روبرو می‌کند .به نظر من از Gzip توکار IIS استفاده شود بهتر است. البته باید ماژول آن در ISS فعال شده باشد.

برای اینکار هم داخل Web.config کد‌های زیر را داخل configuration قرار بدید.


<httpCompression directory="%SystemDrive%\inetpub\temp\IIS Temporary Compressed Files">
      <scheme name="gzip" dll="%Windir%\system32\inetsrv\gzip.dll" staticCompressionLevel="9" />
      <dynamicTypes>
        <add mimeType="text/*" enabled="true" />
        <add mimeType="message/*" enabled="true" />
        <add mimeType="application/x-javascript" enabled="true" />
        <add mimeType="application/javascript" enabled="true" />
        <add mimeType="application/json" enabled="true" />
        <add mimeType="application/json; charset=utf-8" enabled="true" />
        <add mimeType="application/atom+xml" enabled="true" />
        <add mimeType="application/xaml+xml" enabled="true" />
        <add mimeType="*/*" enabled="false" />
      </dynamicTypes>
      <staticTypes>
        <add mimeType="text/*" enabled="true" />
        <add mimeType="message/*" enabled="true" />
        <add mimeType="application/x-javascript" enabled="true" />
        <add mimeType="application/javascript" enabled="true" />
        <add mimeType="application/json" enabled="true" />
        <add mimeType="application/json; charset=utf-8" enabled="true" />
        <add mimeType="application/atom+xml" enabled="true" />
        <add mimeType="application/xaml+xml" enabled="true" />
        <add mimeType="*/*" enabled="false" />
      </staticTypes>
    </httpCompression>
    <urlCompression doStaticCompression="true" doDynamicCompression="true" />
  </system.webServer>
  <location path="Default Web Site">
    <system.webServer>
      <serverRuntime enabled="true"
         frequentHitThreshold="1"
         frequentHitTimePeriod="10:00:00" />
    </system.webServer>
  </location>


نظرات مطالب
ASP.NET MVC #11
درود
پس از خواندن تمامی مطالب سایت ، تا اینجا به این نتیجه رسیدم که آقای نصیری به استفاده از ViewModel به عنوان پلی میان View و Model تاکید دارن.
استفاده از ViewModel‌ها باعث افزایش امنیت در مقابل بعضی از حملات و افزایش Performance با دوری از SELECT * میشه.
اما مطلبی که هنوز برای من روشن نشده رهایی از Duplicate شدن منطق اعتبارسنجی مدل هاست که به صورت Attribute در ViewModel‌ها مشخص می‌شوند.و این تعریف Validation Rule‌ها سیستم رو دچار Duplicated Business می‌کنه.آیا راهی هست که تمامی Validation Rule‌ها در Model انجام شه و به صورت AutoMap بر روی ViewModel‌های متناظر اعمال شود؟
مطالب
Test Driven Development #3
در پست قبلی با  نوشتن یک تست ساده، با مفهوم TDD بیشتر آشنا شدیم .در این پست قصد بر این  است که به وسیله Mvc.Net شروع به نوشتن تست‌های جدی‌تر کرده و از مزایای آن بهره ببریم .
برای  شروع یک پروژه Mvc.Net ساخته و Nunit را در آن نصب می‌کنیم.
مدل زیر را در پوشه مدل‌ها می‌سازیم:
    public class Idea
    {
        public static List<Idea> Ideas = new List<Idea>
            {
                new Idea{Content="سایتی که در ایده به اشتراک گذاشته شود",Title = "سایت ایده ها"},
                new Idea{Content="عینک گوگل را فارسی کنم",Title = "عینک گوگل"},
            };
        public string Content { get;  set;}
        public string Title { get; set; }
    }
  [TestFixture]
    public class IdeaTest
    {
        [Test]
        public void ShouldDisplayListOfIdea()
        {
            var viewResult = new IdeaController().Index() as ViewResult;
            Assert.AreEqual(Idea.Ideas, viewResult.Model)
             Assert.IsNotNull(viewResult.Model);


        }    
 }

کد بالا شامل مقایسه مقدار خروجی Action با لیستی از مدل Idea و همچنین اطمینان از خالی نبودن مدل ارسالی به view می‌باشد.در خط اول یک وهله از Controller می سازیم و Action مورد نظر را به شی از جنس ViewResult  تبدیل(Cast) می‌کنیم پس از آن به وسیله viewResult.Model به مدلی که به سمت view پاس داده می‌شود دسترسی خواهیم داشت.اکنون اگر تست را اجرا  کنیم با خطای کامپایل مواجه می‌شویم.حال Controller و Action مورد نظر را به صورتی  که تست ما پاس شود پیاده سازی می‌کنیم.
    public class IdeaController : Controller
    {

        public ActionResult Index()
        {
            return View(Idea.Ideas);
        }
      }
کد بالا مقدار Ideas را به view برمیگرداند.
در این دروره ما به تست کردن ویو‌ها نخواهیم پرداخت.
تست بعدی تست ساده ای است که فقط میخواهیم از از وجود داشتن یک Action و نام view بازگشتی اطمینان حاصل کنیم.
   [Test]
        public void ShouldLoadCreateIdeaView()
        {
            var viewResult = new IdeaController().Create() as ViewResult;
            Assert.AreEqual(string.Empty, viewResult.ViewName);
        }
در کد بالا مثل تست قبل، یک وهله از Controller می سازیم و سپس نام view بازگشتی را با string.Empty مقایسه میکنیم به این معنی که view خروجی Action ما نباید نامی داشته باشد و براساس قرار دادها باید هم نام اکشن باشد.
حال نوبت به پیاده سازی اکشن رسید.:
        public ActionResult Create()
        {
            return View();
        }
در تست بعدی میخواهیم عملیات اضافه شدن یک Idea را به لیست بررسی کنیم:
    [Test]
        public void ShouldAddIdeaItem()
        {
            var idea = new Idea { Title = "شبکه اجتماعی ", Content = " شبکه اجتماعی سینمایی" };
            var redirectToRouteResult = new IdeaController().Create(idea) as RedirectToRouteResult;
            Assert.Contains(idea, Idea.Ideas);
            Assert.AreEqual("Index",redirectToRouteResult.RouteValues["action"]);
        }
تست بالا نیز مانند دو تست قبل است با این تفاوت که مخواهیم ریدارکت شدن به یک Action  خاص را نیز تست کنیم.برای همین مقدار خروجی را به RedirectToRouteResult تبدیل می‌کنیم.در ادامه یک Idea جدید ساخته و به لست اضافه میکنیم سپس از وجود داشتن آن در لیست Ideas اطمینان حاصل می‌کنیم.در خط آخر نیز نام Action که انتظار داریم بعد از اضافه شدن یک Idea ,کاربر به آن هدایت شود را ست می‌کنیم.
پیاده سازی Action  به شکل زیر است:
        public ActionResult Create(Idea idea)
        {
            Idea.Ideas.Add(idea);
            return RedirectToAction("Index");
        }

در این پست شما با مدل تست نویسی برایMvc.Net آشنا شدید.در مطلب بعدی شما با تست حذف و اصلاح Ideas آشنا خواهید شد. 
مطالب
سرویس جمع و مفرد سازی اسامی

اگر به Entity data model wizard در VS.Net 2010 دقت کرده باشید، گزینه‌ی "Pluralize or singularize generated object names" نیز به آن اضافه شده است:



این مورد از این جهت حائز اهمیت است که عموما نام جداول در بانک اطلاعاتی، جمع است و نام کلاس متناظر ایجاد شده برای آن در کدهای برنامه بهتر است مفرد باشد. برای مثال نام جدول، Customers است و نام کلاس آن بهتر است Customer تعریف گردد. به این صورت کار کردن با آن توسط یک ORM با معناتر خواهد بود؛ زیرا زمانیکه یک وهله از شیء Customer ایجاد می‌شود، فقط یک رکورد از بانک اطلاعاتی مد نظر است؛ در حالیکه یک جدول مجموعه‌ای است از رکوردها.
زبان انگلیسی هم پر است از اسامی جمع و مفرد باقاعده و بی‌قاعده و کل عملیات با اضافه و حذف کردن یک s و یا es پایان نمی‌یابد؛ برای مثال phenomenon و phenomena را در نظر بگیرد تا Money و Moneys.
این امکان مهیا شده توسط Entity Framework 4.0 یا همان EF v2 با برنامه نویسی هم قابل دسترسی است و در اسمبلی System.Data.Entity.Design.dll و فضای نام System.Data.Entity.Design.PluralizationServices قرار گرفته است.
این اسمبلی جزیی از دات نت 4 است و اگر آن‌را توسط گزینه‌ی Add references در VS.NET مشاهده نمی‌کنید، علت آن است که در تنظیمات پروژه جاری، گزینه‌ی Target framework بر روی Client profile قرار گرفته است که باید به دات نت 4 کامل تغییر یابد.
استفاده از آن هم به صورت زیر است:

using System;
using System.Data.Entity.Design.PluralizationServices;
using System.Globalization;

namespace PluralizationServicesTest
{
class Program
{
static void Main(string[] args)
{
var service = PluralizationService.CreateService(CultureInfo.GetCultureInfo("en"));
Console.WriteLine(service.Pluralize("mouse"));
Console.WriteLine(service.IsPlural("phenomena"));
}
}
}

ملاحظات:
این روش فعلا به زبان انگلیسی محدود است و اگر Culture را به مورد دیگری تنظیم کنید با خطای "We don't support locales other than English yet" متوقف خواهید شد.


روش دیگر:
کتابخانه‌ی سورس باز Castle ActiveRecord نیز دارای کلاسی است به نام Inflector که برای همین منظور طراحی شده است:


کاربرد آن در Fluent NHibernate
در Fluent NHibernate کار نگاشت کلاس‌ها به جداول به صورت خودکار صورت می‌گیرد و همچنین تولید ساختار بانک اطلاعاتی نیز به همین نحو می‌باشد. اما می‌توان تولید نام جداول را سفارشی نیز نمود. برای مثال از کلاس Book به صورت خودکار ساختار جدولی به نام Books را تولید کند:
using FluentNHibernate.Conventions;
using FluentNHibernate.Conventions.Instances;
using NHibernate.Helper.Toolkit;

namespace NHibernate.Helper.MappingConventions
{
public class TableNameConvention : IClassConvention
{
public void Apply(IClassInstance instance)
{
instance.Table(Inflector.Pluralize(instance.EntityType.Name));
}
}
}
و برای تزریق آن خواهیم داشت:

... = new AutoPersistenceModel()
.Where(...)
.Conventions.Setup(c =>c.Add<TableNameConvention>())
.AddEntityAssembly(...)
...

مطالب
WF:Windows Workflow #۵
در این قسمت به پیاده سازی یک فرآیند سفارش ساده می‌پردازیم. ابتدا یک پروژه از نوع Workflow Console Application را ایجاد کرده و نام آن را Order Process می‌گذاریم و سپس کلاس‌های زیر را به آن اضافه می‌کنیم:
public class OrderItem
    {
        public int OrderItemID { get; set; }
        public int Quantity { get; set; }
        public string ItemCode { get; set; }
        public string Description { get; set; }
    }

    public class Order
    {
        public Order()
        {
            Items = new List<OrderItem>();
        }
        public int OrderID { get; set; }        
        public string Description { get; set; }         
        public decimal TotalWeight { get; set; }         
        public string ShippingMethod { get; set; }
        public List<OrderItem> Items { get; set; } 
    }
در اینجا دوکلاس تعریف شده است؛ یکی به نام OrderItem می‌باشد که شامل اطلاعات مربوط به میزان سفارش بوده و دیگری کلاس Order می‌باشد که شامل مشخصات سفارش است. سپس فایل OrderWF.xaml را باز کرده و شروع به ساخت فرآیند مورد نظر می‌کنیم. ابتدا یک Sequence را به درون صفحه کشیده و پس از آن در قسمت Arguments دو متغییر را تعریف می‌کنیم. یکی به نام TotalAmount و از نوع Decimal و Out می‌باشد و دیگری به نام OrderInfo که از نوع کلاس Order و In می‌باشد. سپس  یک کنترل WriteLine را به آن اضافه می‌کنیم و در خاصیت Text آن رشته "Order Received" را قرار می‌دهیم. در ادامه یک کنترل Assign را در زیر آن قرار داده و مقدار متغییر TotalAmount را مساوی صفر وارد می‌کنیم.

نکته : برای اینکه نوع متغییر OrderInfo را از نوع کلاس Order قرار دهیم٬ ابتدا DropDown مربوطه را انتخاب کرده و گزینه Browse For Type را انتخاب می‌کنیم تا پنجره مورد نظر باز شود و از طریق آن، کلاس مورد نظر را انتخاب می‌کنیم. اگر در این قسمت کلاس مورد نظر یافت نشد، نیاز است ابتدا عمل Build Project را یک بار انجام دهیم.

 بعضی از کنترل‌های Workflow در قسمت Toolbox موجود نمی‌باشند. از جمله این کنترل‌ها می‌توان به کنترل Add اشاره کرد. برای استفاده از این کنترل، ابتدا باید آن را به لیست کنترل‌ها اضافه نمود. جهت این امر٬ ابتدا در قسمت Toolbox یک Tab جدید را با نام دلخواه ایجاد کرده و سپس بر روی Tab کلیک راست نموده و گزینه Choose Items را انتخاب می‌کنیم. سپس از قسمت System.Activities.Components کنترل Add را انتخاب کرده و سپس بر روی دکمه OK کلیک می‌نمائیم. حال کنترل Add به لیست کنترل‌ها در Tab مورد نظر اضافه شده است.
در ادامه یک کنترل Switch را به فرایند خود اضافه کرده و مقدار T آن را برابر String قرار می‌دهیم؛ زیرا نوع داده‌ای که در قسمت Expression کنترل Switch قرار می‌گیرد، از نوع رشته می‌باشد. پس از اضافه کردن کنترل مورد نظر، کد زیر را به قسمت Expression کنترل اضافه خواهیم کرد:
OrderInfo.ShippingMethod
سپس در کنترل Switch، بر روی قسمت Add new case کلیک کرده و رشته‌های مورد نظر را اضافه می‌کنیم که شامل  "" NextDay"" و  ""2ndDay"" می‌باشند. اکنون در بدنه هر دو Case، کنترل Add را اضافه می‌کنیم. در هنگام اضافه کردن باید برای سه خصوصیت، نوع مشخص شود و نوع هر سه را برابر Decimal قرار می‌دهیم.
در ادامه کنترل Add را انتخاب کرده و به خاصیت Right آنها به ترتیب مقدار های 10.0m و 15.0m را اضافه می‌کنیم و برای خصوصیت Result هر دو کنترل، متغیر TotalAmount را انتخاب می‌کنیم. سپس یک کنترل Assign را به صفحه اضافه کرده و در قسمت To، متغییر  TotalAmount را قرار می‌دهیم و در قسمت Value کد زیر را:
TotalAmount + (OrderInfo.TotalWeight * 0.50m) 
و در آخر با ستفاده از کنترل WriteLine به چاپ محتوای متغییر TotalAmount می‌پر‌دازیم.

اکنون برای اینکه بتوانیم برنامه را اجرا کنیم، کد زیر را به کلاس Program.cs اضافه می‌کنیم:
static void Main(string[] args)
        {
            Order myOrder = new Order
            {
                OrderID = 1,
                Description = "Need some stuff",
                ShippingMethod = "2ndDay",
                TotalWeight = 100
            };
            IDictionary<String, object> input = new Dictionary<String, Object>
            {
                { "OrderInfo",myOrder}
            };
            IDictionary<String, Object> output = WorkflowInvoker.Invoke(new OrderWF(), input);
            Decimal total = (Decimal)output["TotalAmount"];
            Console.WriteLine("Workflow returned ${0} for my order total", total);
            Console.WriteLine("Press ENTER to exit"); 
            Console.ReadLine();

            //Activity workflow1 = new OrderWF();
            //WorkflowInvoker.Invoke(workflow1);
        }
در اینجا علت استفاده از IDictionary، نوع خروجی متد Invoke می‌باشد. در ادامه به کامل کردن این مثال پرداخته می‌شود.
مطالب
مبانی TypeScript؛ کلاس‌ها
تا قبل از ES 6 در جاوا اسکریپت از توابع جهت ایجاد کامپوننت‌هایی با قابلیت استفاده مجدد استفاده می‌شد. این امر برای برنامه‌نویسانی که با زبان‌های OOP آشنایی دارند، شاید چندان خوشایند نباشد. در TypeScript نیز همانند ES 6 امکان استفاده از کلاس‌ها مهیا است.
در حالت کلی یک کلاس قالبی برای ایجاد اشیاء است. تمامی اشیاء ایجاد شده از این الگو دارای یکسری پراپرتی و متد می‌باشند. از پراپرتی‌ها جهت تعریف وضعیت‌ها و از متدها جهت تعریف رفتارها استفاده خواهد شد. همچنین مزیت اصلی یک کلاس، کپسوله‌سازی قابلیت‌های یک موجودیت خاص است. همانند دیگر زبان‌های شیءگرا، در TypeScript نیز یک کلاس می‌تواند ویژگی‌های زیر را داشته باشد:
  • سازنده (constructor)
  • پراپرتی، متد
  • Access Modifiers
  • ارث‌بری
  • کلاس‌های Abstract
در ادامه هر کدام از موارد فوق را بررسی خواهیم کرد.

سازنده (Constructor)
از سازنده‌ها جهت مقداردهی وهله‌های یک کلاس استفاده می‌شود. در ادامه یک کلاس جدید را با استفاده از کلمه‌ی کلیدی class ایجاد کرده‌ایم. این کلاس دارای یک سازنده است:
class ReferenceItem {
    constructor(title: string, publisher?: string) {
        // perform initialization here
    }
}
همانطور که مشاهده می‌کنید یک سازنده شبیه به یک متد است؛ با این تفاوت که برای نام آن از کلمه کلیدی constructor استفاده می‌شود. در TypeScript برای یک کلاس تنها یک سازنده را می‌توانیم داشته باشیم. البته در دیگر زبان‌های برنامه‌نویسی امکان تعریف چندین سازنده را با پارامترهای مختلف برای یک کلاس می‌توانید داشته باشید. برای رسیدن به این هدف در TypeScript می‌توان از Optional Parameters استفاده کرد. برای ایجاد یک وهله از کلاس فوق می‌توانیم به این صورت عمل کنیم:
let encyclopedia = new ReferenceItem('WorldPedia', 'WorldPub');
در کد فوق با استفاده از کلمه‌ی کلیدی new یک وهله از کلاس ReferenceItem را ایجاد کرده‌ایم و در نهایت آن را به متغیری با نام encyclopedia انتساب داده‌ایم. یعنی در واقع با استفاده از new توانسته‌ایم سازنده‌ی کلاس را فراخوانی کرده و سپس وهله‌ایی از آن را به متغیر ذکر شده انتساب دهیم.

پراپرتی، متد 
همانند اینترفیس‌ها، کلاس‌ها نیز می‌توانند پراپرتی و متد داشته باشند. با این تفاوت که در کلاس‌ها جزئیات پیاده‌سازی نیز ذکر خواهد شد. در یک کلاس به دو روش متفاوت می‌توانیم پراپرتی را تعریف کنیم. روش اول همانند تعریف یک متغیر است. به عنوان مثال در کلاس زیر یک پراپرتی با نام numberOfPages را از نوع عددی تعریف کرده‌ایم:
class ReferenceItem {
    numberOfPages: number;
}
برای دسترسی به این پراپرتی می‌توانیم از سینتکس نقطه (.) استفاده کنیم. روش دوم برای تعریف یک پراپرتی، ایجاد accessor‌های سفارشی است. accessors در واقع توابع getter و setter هستند که به شما در نحوه‌ی get و set کردن یک پراپرتی کمک خواهند کرد:
class ReferenceItem {
    numberOfPages: number;
    
    get editor(): string {
        // custom getter logic goes here, should return a value
    }
    
    set editor(newEditor: string) {
        // custom setter logic goes here
    }
}
همانطور که مشاهده می‌کنید، accessorهایی را برای پراپرتی editor با استفاده از کلمات کلیدی get و set ایجاد کرده‌ایم. این accessorها در واقع توابعی همنام هستند. تابع get همیشه فاقد پارامتر است. می‌توانیم برای تابع get نوع برگشتی را نیز تعیین کنیم (به عنوان مثال در کد فوق نوع برگشتی string است). setter نیز باید تنها یک پارامتر از ورودی دریافت کند. همچنین نمی‌توانیم برای آن نوع برگشتی را تعیین کنیم. درون بدنه‌ی این accessorها می‌توانیم هر نوع کنترلی را بر روی پراپرتی داشته باشیم. برای دسترسی این accessorها نیز باید از سینتکس نقطه (.) استفاده کنیم.
متدها نیز توابعی هستند که درون یک کلاس تعریف می‌شوند. برای نمونه در کد زیر یک تابع با نام printChapterTitle را تعریف کرده‌ایم که یک پارامتر را از ورودی دریافت کرده و هیچ مقداری را در خروجی بر نمی‌گرداند:
class ReferenceItem {
    numberOfPages: number;
    
    get editor(): string {
        // custom getter logic goes here, should return a value
    }
    
    set editor(newEditor: string) {
        // custom setter logic goes here
    }
    
    printChapterTitle(chapterNum: number): void {
        // print title here
    }
}

Parameter properties
در حالت عادی برای مقداردهی اولیه‌ی پراپرتی‌ها یک شیء می‌توانیم یکسری پارامتر را برای سازنده کلاس تعریف کرده و درون سازنده، پراپرتی‌های موردنیازمان را مقداردهی کنیم:
class Author {
    name: string;
    
    constructor(authorName: string) {
        name = authorName;
    }
}
با کمک Parameter properties می‌توانیم به صورت خلاصه‌تری اینکار را انجام دهیم:
class Author {
    constructor(public name: string){}
}
همانطور که مشاهده می‌کنید اینکار را با افزودن کلمه‌ی کلیدی public به ابتدای پارامتر name انجام داده‌ایم. در این‌حالت دیگر نیازی به تعریف یک پراپرتی اضافی درون کلاس نخواهیم داشت. کامپایلر TypeScript خودش یک پراپرتی را با همین نام ایجاد کرده و مقدار دریافتی از سازنده را برای آن ست خواهد کرد.

Static Properties
تاکنون درباره‌ی اعضای مربوط به هر وهله از کلاس‌ها صحبت کردیم؛ یعنی اعضایی که در زمان وهله‌سازی در دسترس خواهند بود. در واقع می‌توانیم اعضای استاتیک را نیز برای کلاس‌ها داشته باشیم. منظور از استاتیک این است که مقادیر یک عضوء استاتیک در وهله‌های مختلف یک شیء، متفاوت نیست. بلکه یک مقدار آن برای تمامی وهله‌ها به اشتراک گذاشته خواهد شد:
class Library {
    constructor(public name: string) {}
    
    static description: string = 'A source of knowledge';
}

let lib = new Library('New York Public Library');
console.log(lib.name); // available on instances of the class

console.log(Library.description);

Access Modifiers
با استفاده از Access Modifier می‌توانیم میدان دید یک پراپرتی و یا یک متد را برای مصرف کننده‌ی کلاس کنترل کنیم. TypeScript دارای سه Access Modifier است:
public: در حالت پیش‌فرض تمامی اعضای یک کلاس عمومی (public) هستند. در نتیجه لزومی به ذکر آن برای پراپرتی‌ها و متدها نیست. یک حالت استثناء، استفاده از Parameter properties است. در این حالت باید کلمه‌ی کلیدی public حتماً ذکر شود. 
private: برای محدود کردن دسترسی اعضای یک کلاس می‌توانید از کلمه‌ی کلیدی private استفاده کنید. در این‌حالت مصرف کننده‌ی کلاس به اعضای خصوصی (private) دسترسی نخواهد داشت. 
protected: این modifier نیز شبیه به private عمل می‌کند، با این تفاوت که توسط subclassهای مربوط به کلاس تعریف شده در آن نیز قابل دسترس است.


Inheritance
منظور از Inheritance یا ارث‌بری، اشتراک‌گذاری تعاریف یک کلاس برای یک یا چند sub-class است. فرض کنید یک کلاس با نام ReferenceItem با یکسری اعضای تعریف شده درون آن داریم و می‌خواهیم دو کلاس مشتق شده را از این کلاس تهیه کنیم. در این‌حالت کلاس ReferenceItem کلاس پایه (base class) و کلاس‌های مشتق شده از آن sub-class نامیده می‌شوند. بنابراین وهله‌های ایجاد شده از کلاس‌های مشتق شده دارای پراپرتی‌های کلاس پایه نیز خواهند بود. برای داشتن قابلیت ارث‌بری در TypeScript می‌توانیم به اینصورت عمل کنیم:
class ReferenceItem {
    title: string;
    printItem(): void { 
        // print something here 
    }
}

class Journal extends ReferenceItem {
    constructor() {
        super();
    }
    
    contributors: string[];
}
همانطور که مشاهده می‌کنید با استفاده از کلمه‌ی کلیدی extends توانسته‌ایم یک sub-class ایجاد کنیم. بنابراین وهله‌های کلاس Journal علاوه بر پراپرتی‌های خود (در اینجا contributors ) دارای پراپرتی title و همچنین متد printItem نیز هستند. نکته‌ایی که در اینجا وجود دارد این است که تمامی sub-classها یا کلاس‌های مشتق شده باید درون سازنده‌ی خود، تابع super را فراخوانی کنند؛ با اینکار سازنده‌ی کلاس پایه فراخوانی خواهد شد.
لازم به ذکر است که می‌توان متدهای کلاس پایه را درون کلاس‌های مشتق شده، override کرد. برای اینکار کافی است متد موردنظر در کلاس پایه را درون کلاس مشتق شده مجدداً تعریف کرده و منطق موردنظر را درون آن نوشت:
class Journal extends ReferenceItem {
    constructor() {
        super();
    }
    
    printItem(): void { 
        super.printItem();
        console.log('message from Journal');
    }
    
    contributors: string[];
}
با استفاده از super.printItem به کامپایلر TypeScript گفته‌ایم که تمامی کدهای درون متد printItem در کلاس پایه نیز اجرا شوند. اگر مایل بودید می‌توانید از آن صرفنظر کنید.

Abstract Classes 
کلاس‌های Abstract یک نوع خاص از کلاس‌ها هستند که نمی‌توان آنها را وهله‌سازی کرد. یعنی تنها برای تعریف کلاس‌های پایه از آنها استفاده خواهد شد. این نوع کلاس‌ها شبیه به اینترفیس‌ها هستند؛ اما ممکن است دارای پیاده‌سازی نیز باشند. در ادامه یک نمونه از abstract class را مشاهده می‌کنید:
abstract class ReferenceItem {
    private _publisher: string;
    static departement: string = 'Research';
    
    constructor(public title: string, protected year: number) {
        
    }
    
    printItem(): void {
        console.log('message from abstract class');
    } 
    
    get publisher(): string {
        return this._publisher.toUpperCase();
    }
    
    set publisher(newPublisher: string) {
        this._publisher = newPublisher;
    }
    
    abstract printCitation(): void;
}

class Encyclopedia extends ReferenceItem {
    
    constructor(newTitle: string, newYear, public edition: number) {
        super(newTitle, newYear);
    }
    
    printCitation(): void {
        console.log('message');
    }
}

let test = new Encyclopedia('WorldPerdia', 1900, 10);
test.printItem();
همانطور که مشاهده می‌کنید درون یک کلاس abstract می‌توانیم متدهای abstract را نیز داشته باشیم؛ یعنی تنها امضای متد را تعیین کرده و پیاده‌سازی آن را به کلاس‌های مشتق شده واگذار کنیم. 
اشتراک‌ها
کتابخانه ای جهت دریافت اطلاعات کلاینت در Asp.Net Core

 Get Browser Info,Os Info and Device Type

public void ConfigureServices(IServiceCollection services)
{
// Add browser detection services.
    services.AddDetection()
                 .AddDevice();
.AddBrowser()   // concept
.AddEngine()    // concept
.AddPlatform(); // concept

    // Add framework services.
    services.AddMvc();
}

public class HomeController : Controller
{    
    private readonly IUserAgent _useragent;    
    private readonly IBrowser _browser;
    private readonly IEngine _engine;
    private readonly IPlatform _platform;
private readonly IUserAgent _useragent;
    private readonly IDevice _device; 

    public HomeController(IBrowserResolver browserResolver, 
        IEngineResolver engineResolver, IDeviceResolver deviceResolver,
        IPlatformResolver platformResolver)
    {
        _useragent = browserResolver.UserAgent,        
        _browser = browserResolver.Browser,
        _engine = engineResolver.Engine,
        _platform = platformResolver.Platform
        _useragent = deviceResolver.UserAgent,
        _device = deviceResolver.Device
    }

    public IActionResult Index()
    {            
        return View();
    }
}


   
کتابخانه ای جهت دریافت اطلاعات کلاینت در Asp.Net Core