مطالب
دریافت اطلاعات از پایگاه داده بواسطه Stored Procedure در EF Core 2.0
همواره در تکنولوژی  EF CodeFirst، چه در ASP.NET MVC و چه در ASP.NET Core، استفاده از امکانات بومی پایگاه‌های داده با محدودیت‌هایی مواجه بوده‌است. یکی از این اشکالات، عدم توانایی این تکنولوژی در گرفتن لیستی از اطلاعات که منطبق بر بیشتر از یک مدل می‌باشد، هست. در این مقاله تمرکز بر روی رفع این اشکال، بدون نیاز به اضافه کردن مدخل جدیدی به پروژه می‌باشد. بنابراین پیشنیاز ضروری این مبحث، مطالعه «شروع به کار با EF Core 1.0» ، مخصوصا «استفاده از امکانات بومی بانک‌های اطلاعاتی» است.

Stored Procedure چیست ؟

Stored Procedure  یا  SP  یا به زبان فارسی «رویه‌های ذخیره شده» اشیایی اجرا پذیر در بانک اطلاعاتی SQL Server هستند که شامل یک یا چندین دستور SQL می‌شوند. این رویه‌ها می‌توانند پارامتر‌های ورودی و خروجی داشته باشند؛ همچنین می‌توانند لیستی از موجودیت‌ها را نیز برگردانند و یا می‌توان داخل این رویه‌ها به زبان T-SQL برنامه نویسی کرد.
مهم‌ترین کاربر این رویه‌ها، ذخیره کردن دستورات Select , Insert , Update , Delete هست و یا ترکیبی از این‌ها .


اشکال راه حل‌های پیش فرض مبتنی بر Context

برای استفاده از راه حل‌های پیش فرض  مبتنی بر Context، همانطور که در مقاله «استفاده از امکانات بومی بانک‌های اطلاعاتی» به آن پرداخته شده، سه روش کلی برای استفاده از Stored Procedure  پیشنهاد شده‌است:
- روش اول استفاده از متد fromsql است. اشکال این متد، محدودیت استفاده برای یک موجودیت برنامه  است و به زبان ساده نمی‌توان در کوئری پایگاه داده از join  استفاده کرد.
- روش دوم استفاده از متد ExecuteSqlCommand موجود در context برنامه است . اشکال این متد void بودن آن است که باعث می‌شود بازگشتی از پایگاه داده حاصل نشود.
- روش سوم استفاده از متد ExecuteScalar  موجود در Context برنامه است. اشکالی که به این متد گرفته می‌شود، Scalar  بودن مقدار بازگشتی از آن است که باعث می‌شود نتوانیم لیستی از موجودیت‌ها را به ViewModel مورد نظر نگاشت کنیم.

راه حل این مشکل

برای حل این مشکلات که بسیار هم مهم هستند، اول باید قطعه کد زیر را به Context برنامه اضافه نمود:
public void OpenConnection()
{
   Database.OpenConnection();
}

public DbCommand Command()
{
   DbCommand cmd = Database.GetDbConnection().CreateCommand();
   return cmd;
}
سپس در اینترفیس IUnitOfWork  که در مطلب «لایه بندی و تزریق وابستگی‌ها» در مورد آن بحث شده، متد OpenConnection و Command را اضافه می‌کنیم:
void OpenConnection();
DbCommand Command();
حال کلاس و اینترفیس جدیدی را برای پیاده سازی سرویس اتصال به Stored Procedure ایجاد کرده و  در کلاس آغازین برنامه، به‌صورت AddScopped این سرویس را برای استفاده از تزریق وابستگی توکار ASP.NET Core  معرفی می‌کنیم:
public void ConfigureServices(IServiceCollection services)
{
     services.AddScoped<IUnitOfWork, ApplicationDbContext>();
     services.AddScoped<ISpReader, SpReader>();
}
سپس در سازنده کلاس این سرویس، اینترفیس IUnitOfWork را تزریق کرده تا بتوانیم از متد‌های نوشته شده در Context استفاده کنیم. حال اقدام به پیاده سازی متد GetFromSp بصورت زیر می‌کنیم :
public List<ViewModel> GetFromSp <ViewModel>(string[,] Parametr, string NameSp) where ViewModel  : new()
        {
            _uow.OpenConnection();
            DbCommand cmd = _uow.Command();
            cmd.CommandText = NameSp;
            cmd.CommandType = CommandType.StoredProcedure;
            var countParametr = Parametr.GetLength(0);

            for (int i = 0; i < countParame tr; i++)
            {
                cmd.Parameters.Add(new SqlParameter { ParameterName = Parametr[i, 0], Value = Parametr[i, 1] });
            }

            List<ViewModel> list = new List<ViewModel >();
            using (var reader = cmd.ExecuteReader())
            {
                if (reader != null && reader.HasRows)
                {
                    var entity = typeof(ViewModel);
                    var propDict = new Dictionary<string, PropertyInfo>();
                    var props = entity.GetProperties
           (BindingFlags.Instance | BindingFlags.Public);
                    propDict = props.ToDictionary(p => p.Name.ToUpper(), p => p);
                    while (reader.Read())
                    {
                        ViewModel  newobject = new ViewModel ();

                        for (int index = 0; index < reader.FieldCount; index++)
                        {
                            if (propDict.ContainsKey(reader.GetName(index).ToUpper()))
                            {
                                var info = propDict[reader.GetName(index).ToUpper()];
                                if ((info != null) && info.CanWrite)
                                {
                                    var val = reader.GetValue(index);
                                    info.SetValue(newobject, (val == DBNull.Value) ? null : val, null);
                                }
                            }
                        }
                        list.Add(newobject);
                    }

                }
                return list;
            }
در این متد، اول با استفاده از OpenConnection، اتصالی را به پایگاه داده، باز کرده سپس شیئ از DbCommand را می‌سازیم و نام Stored Procedure و نوع کوئری ارسالی را معین می‌کنیم. حال با استفاده از  حلقه for، نام و مقدار پارامتر‌های ارسال شده به متد را به شیئ cmd اضافه می‌کنیم. در مرحله بعد، لیستی را از کلاس مدلی که باید مقادیر بازگشتی به آن نگاشت شوند و بعنوان کلاس جنریک به متد ارسال شده است، می‌سازیم. با متد ExecuteReader که در شیئ ساخته شده از Command موجود می‌باشد، اقدام به خواندن اطلاعات از Stored Procedure کرده و در شیئ Reader نگه داری می‌کنیم و سپس اطلاعات خوانده شده را با استفاده از Dictionary و متد Add به لیست ساخته شده اضافه می‌کنیم. در آخر لیست ساخته شده در حلقه While را بعنوان نتیجه متد باز می‌گردانیم.

همچنین می‌توان برای استفاده این متد برای رویه‌های بدون پارامتر ورودی، از OverLoad این متد، با حذف قطعات کد زیر:
var countParametr = Parametr.GetLength(0);
for (int i = 0; i < countParametr; i++)
{
     cmd.Parameters.Add(new SqlParameter { ParameterName = Parametr[i, 0], Value = Parametr[i, 1] });
}
و حذف آرایه string[,] Parameter  از ورودی متد، استفاده نمود .

روش استفاده از این متد

برای استفاده از این متد، لازم است چند نکته رعایت شوند:
1- خروجی Stored Procedure دقیقا منطبق بر ViewModel ارسالی به متد جهت تشکیل لیست باشد.
2- لیست پارامتر‌ها باید بصورت آرایه دوبعدی باشد که اندازه بعد اول، تعداد پارامتر‌ها و اندازه بعد دوم 2 باشد.
3- در ماتریسی که از این پارامتر‌ها ساخته می‌شود، ستون اول نام پارامتر و ستون دوم مقدار پارامتر ست می‌شود.

بطور مثال Stored Procedure  زیر حاوی سه پارامتر است :
CREATE PROCEDURE [dbo].[isRelation](
@TableName as varchar(50),
@FieldOfRelation as varchar(70),
@ValueOfField as int)
برای دسترسی به این رویه ابتدا در سرویس استفاده کننده، ISpReader را تزریق می‌کنیم و سپس بصورت زیر مقدمات استفاده از این سرویس را فراهم می‌کنیم:

public class EntityServices : IEntityService
    {
        private ISpreader _Reader;
        public EntityServices( ISpreader reader)
        {
            _Reader = reader;
        }

        public List<StoreProcedureResultViewModel>  IsRelation(string tableName , int keyValue, string keyFieldName)
        {
            List<StoreProcedureResultViewModel> IsContact;
            try
            {
                string[,] Parametr = new string[3, 2];
                Parametr[0, 0] = "@TableName";
                Parametr[0, 1] = tableName ;
                Parametr[1, 0] = "@ValueOfField";
                Parametr[1, 1] = keyValue.ToString().Trim();
                Parametr[2, 0] = "@FieldOfRelation";
                Parametr[2, 1] = keyFieldName.Trim();
                IsContact = _Reader.GetSp<StoreProcedureResultViewModel>(Parametr, "IsRelation");
                return IsContact;
            }
            catch (Exception ex)
            {
            }
        }
    }
بدین ترتیب با استفاده از این متد توانستیم لیستی از ViewModel منطبق بر خروجی Stored Procedure  را بدست آوریم.  
اشتراک‌ها
روش فعالسازی IE Mode در Chromium Edge

As you may remember, Microsoft has removed the IE Mode feature from Microsoft Edge. THe company has limited it to enterprise customers and IT admins. They have also stated that the feature was released to the public for testing purposes only. Here is a bit of good news - you can re-enable IE Mode when needed with recent versions of Edge! 

روش فعالسازی IE Mode در Chromium Edge
مطالب
ایجاد سرویس چندلایه‎ی WCF با Entity Framework در قالب پروژه - 2
برای استفاده از کلاس‏های Entity که در نوشتار پیشین ایجاد کردیم در WCF باید آن کلاس‎ها را دست‎کاری کنیم. متن کلاس tblNews را در نظر بگیرید:
namespace MyNewsWCFLibrary
{
    using System;
    using System.Collections.Generic;
    
    public partial class tblNews
    {
        public int tblNewsId { get; set; }
        public int tblCategoryId { get; set; }
        public string Title { get; set; }
        public string Description { get; set; }
        public System.DateTime RegDate { get; set; }
        public Nullable<bool> IsDeleted { get; set; }
    
        public virtual tblCategory tblCategory { get; set; }
    }
}
 مشاهده می‌کنید که برای تعریف کلاس‌ها از کلمه کلیدی partial استفاده شده است.  استفاده از کلمه کلیدی partial به شما اجازه می‌دهد که یک کلاس را در چندین فایل جداگانه تعریف کنید. به عنوان مثال می‌توانید فیلدها، ویژگی ها و سازنده‌ها را در یک فایل و متدها را در فایل دیگر قرار دهید. 
به صورت خودکار کلیه‌ی ویژگی‌ها به توجه به پایگاه داده ساخته شده اند. برای نمونه ما برای فیلد IsDeleted در SQL Server ستون Allow Nulls را کلیک کرده بودیم که در نتیجه در اینجا عبارت Nullable پیش از نوع فیلد نشان داده شده است. برای استفاده از این کلاس در WCF باید صفت  DataContract را به کلاس داد. این قرارداد به ما اجازه استفاده از ویژگی‌هایی که صفت DataMember را می‌گیرند را می‌دهد.
کلاس بالا را به شکل زیر بازنویسی کنید:
using System.Runtime.Serialization;

namespace MyNewsWCFLibrary
{
    using System;
    using System.Collections.Generic;
    
    [DataContract]
    public partial class tblNews
    {
        [DataMember]
        public int tblNewsId { get; set; }
        [DataMember]
        public int tblCategoryId { get; set; }
        [DataMember]
        public string Title { get; set; }
        [DataMember]
        public string Description { get; set; }
        [DataMember]
        public System.DateTime RegDate { get; set; }
        [DataMember]
        public Nullable<bool> IsDeleted { get; set; }

        public virtual tblCategory tblCategory { get; set; }
    }
}
هم‌چنین کلاس tblCategory را به صورت زیر تغییر دهید:
namespace MyNewsWCFLibrary
{
    using System;
    using System.Collections.Generic;
    using System.Runtime.Serialization;

    [DataContract]
    public partial class tblCategory
    {
        public tblCategory()
        {
            this.tblNews = new HashSet<tblNews>();
        }

        [DataMember]
        public int tblCategoryId { get; set; }
        [DataMember]
        public string CatName { get; set; }
        [DataMember]
        public bool IsDeleted { get; set; }
    
        public virtual ICollection<tblNews> tblNews { get; set; }
    }
}
با انجام کد بالا از بابت مدل کارمان تمام شده است. ولی فرض کنید در اینجا تصمیم به تغییری در پایگاه داده می‌گیرید. برای نمونه می‌خواهید ویژگی Allow Nulls فیلد IsDeleted را نیز False کنیم و مقدار پیش‌گزیده به این فیلد بدهید. برای این کار باید دستور زیر را در SQL Server اجرا کنیم:
BEGIN TRANSACTION
GO
ALTER TABLE dbo.tblNews
DROP CONSTRAINT FK_tblNews_tblCategory
GO
ALTER TABLE dbo.tblCategory SET (LOCK_ESCALATION = TABLE)
GO
COMMIT
BEGIN TRANSACTION
GO
CREATE TABLE dbo.Tmp_tblNews
(
tblNewsId int NOT NULL IDENTITY (1, 1),
tblCategoryId int NOT NULL,
Title nvarchar(50) NOT NULL,
Description nvarchar(MAX) NOT NULL,
RegDate datetime NOT NULL,
IsDeleted bit NOT NULL
)  ON [PRIMARY]
 TEXTIMAGE_ON [PRIMARY]
GO
ALTER TABLE dbo.Tmp_tblNews SET (LOCK_ESCALATION = TABLE)
GO
ALTER TABLE dbo.Tmp_tblNews ADD CONSTRAINT
DF_tblNews_IsDeleted DEFAULT 0 FOR IsDeleted
GO
SET IDENTITY_INSERT dbo.Tmp_tblNews ON
GO
IF EXISTS(SELECT * FROM dbo.tblNews)
 EXEC('INSERT INTO dbo.Tmp_tblNews (tblNewsId, tblCategoryId, Title, Description, RegDate, IsDeleted)
SELECT tblNewsId, tblCategoryId, Title, Description, RegDate, IsDeleted FROM dbo.tblNews WITH (HOLDLOCK TABLOCKX)')
GO
SET IDENTITY_INSERT dbo.Tmp_tblNews OFF
GO
DROP TABLE dbo.tblNews
GO
EXECUTE sp_rename N'dbo.Tmp_tblNews', N'tblNews', 'OBJECT' 
GO
ALTER TABLE dbo.tblNews ADD CONSTRAINT
PK_tblNews PRIMARY KEY CLUSTERED 
(
tblNewsId
) WITH( STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]

GO
ALTER TABLE dbo.tblNews ADD CONSTRAINT
FK_tblNews_tblCategory FOREIGN KEY
(
tblCategoryId
) REFERENCES dbo.tblCategory
(
tblCategoryId
) ON UPDATE  NO ACTION 
 ON DELETE  NO ACTION 

GO
COMMIT
پس از آن مدل Entity Framework را باز کنید و در جایی از صفحه راست‌کلیک کرده و از منوی بازشده گزینه Update Model from Database را انتخاب کنید. سپس در پنجره بازشده، چون هیچ جدول، نما یا روالی به پایگاه داده‌ها نیفزوده ایم؛ دگمه‌ی Finish را کلیک کنید. دوباره کلاس tblNews را بازکنید. متوجه خواهید شد که همه‌ی DataContractها و DataMemberها را حذف شده است. ممکن است بگویید می‌توانستیم کلاس یا مدل را تغییر دهیم و به وسیله‌ی Generate Database from Model به‌هنگام کنیم. ولی در نظر بگیرید که نیاز به ایجاد چندین جدول دیگر داریم و مدلی با ده‌ها Entity دارید. در این صورت همه‌ی تغییراتی که در کلاس داده ایم زدوده خواهد شد. 
در بخش پسین، درباره‌ی این‌که چه کنیم که عبارت‌هایی که به کلاس‌ها می‌افزاییم حذف نشود؛ خواهم نوشت.
مطالب
مفاهیم برنامه نویسی ـ آشنایی با سازنده‌ها
در مطلب پیشین برای نگهداری حالت شیء یا همان ویژگی‌های آن Property‌‌‌ها را در کلاس معرفی کردیم و پس از ایجاد شیء مقدار مناسبی را به پروپرتی‌ها اختصاص دادیم.
اگرچه ایجاد شیء و مقداردهی به ویژگی‌های آن ما را به هدفمان می‌رساند، اما بهترین روش نیست چرا که ممکن است مقداردهی به یک ویژگی فراموش شده و سبب شود شیء در وضعیت نادرستی قرار گیرد. این مشکل با استفاده از سازنده‌ها (Constructors) حل می‌شود.
سازنده (Constructor) عضو ویژه ای از کلاس است بسیار شبیه به یک متد که در هنگام وهله سازی (ایجاد یک شیء از کلاس) به صورت خودکار فراخوانی و اجرا می‌شود. وظیفه سازنده مقداردهی به ویژگی‌های عمومی و خصوصی شیء تازه ساخته شده و به طور کلی انجام هر کاری است که برنامه‌نویس در نظر دارد پیش از استفاده از شیء به انجام برساند.
مثالی که در این بخش بررسی می‌کنیم کلاس مثلث است. برای پیاده سازی این کلاس سه ویژگی در نظر گرفته ایم. قاعده، ارتفاع و مساحت. بله مساحت را این بار به جای متد به صورت یک پروپرتی پیاده سازی می‌کنیم. اگرچه در آینده بیشتر راجع به چگونگی انتخاب برای پیاده سازی یک عضو کلاس به صورت پروپرتی یا متد بحث خواهیم کرد اما به عنوان یک قانون کلی در نظر داشته باشید عضوی که به صورت منطقی به عنوان داده مطرح است را به صورت پروپرتی پیاده سازی کنید. مانند نام دانشجو. از طرفی اعضایی که دلالت بر انجام عملی دارند را به صورت متد پیاده سازی می‌کنیم. مانند متد تبدیل به نوع داده دیگر. (مثلاً ()Object.ToString)
public class Triangle
{
    private int _height;
    private int _baseLength;

    public int Height
    {
        get { return _height; }

        set
        {
            if (value < 1 || value > 100)
            {
                // تولید خطا
            }

            _height = value;
        }
    }

    public int BaseLength
    {
        get { return _baseLength; }

        set
        {
            if (value < 1 || value > 100)
            {
                // تولید خطا
            }

            _baseLength = value;
        }
    }

    public double Area
    {
        get { return _height * _baseLength * 0.5; }
    }
}
چون در بخشی از یک پروژه نیاز پیدا کردیم با یک سری مثلث کار کنیم، کلاس بالا را طراحی کرده ایم. به نکات زیر توجه نمایید.
 • در اکسسور set دو ویژگی قاعده و ارتفاع، محدوده مجاز مقادیر قابل انتساب را بررسی نموده ایم. در صورتی که مقداری خارج از محدوده یاد شده برای این ویژگی‌ها تنظیم شود خطایی را ایجاد خواهیم کرد. شاید برای برنامه نویسانی که تجربه کمتری دارند زیاد روش مناسبی به نظر نرسد. اما این یک روش قابل توصیه است. مواجه شدن کد مشتری (کد استفاده کننده از کلاس) با یک خطای مهلک که علت رخ دادن خطا را نیز می‌توان به همراه آن ارائه کرد بسیار بهتر از بروز خطاهای منطقی در برنامه است. چون رفع خطاهای منطقی بسیار دشوارتر است. در مطالب آینده راجع به تولید خطا و موارد مرتبط با آن بیشتر صحبت می‌کنیم.
 • در مورد ویژگی مساحت، اکسسور set را پیاده سازی نکرده ایم تا این ویژگی را به صورت فقط خواندنی ایجاد کنیم.

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

سازنده پیش فرض

سازنده‌ها مانند متدهای دیگر می‌توانند پارامتر دریافت کنند. سازنده ای که هیچ پارامتری دریافت نمی‌کند سازنده پیش فرض (Default constructor) نامیده می‌شود. سازنده پیش فرض زمانی اجرا می‌شود که با استفاده از عملگر new شیء ای ایجاد می‌کنید اما هیچ آرگومانی را برای این عملگر در نظر نگرفته اید.
اگر برای کلاسی که طراحی می‌کنید سازنده ای تعریف نکرده باشید کامپایلر سی شارپ یک سازنده پیش فرض (بدون پارامتر) خواهد ساخت. این سازنده هنگام ایجاد اشیاء فراخوانی شده و مقدار پیش فرض متغیرها و پروپرتی‌ها را با توجه به نوع آن‌ها تنظیم می‌نماید. مثلاً مقدار صفر برای متغیری از نوع int یا false برای نوع bool و null برای انواع ارجاعی که در آینده در این مورد بیشتر خواهید آموخت.
اگر مقادیر پیش فرض برای متغیرها و پروپرتی‌ها مناسب نباشد، مانند مثال ما، سازنده پیش فرض ساخته شده توسط کامپایلر همواره شیء ای می‌سازد که وضعیت صحیحی ندارد و نمی‌تواند وظیفه خود را انجام دهد. در این گونه موارد باید این سازنده را جایگزین نمود.

جایگزینی سازنده پیش فرض ساخته شده توسط کامپایلر

افزودن یک سازنده صریح به کلاس بسیار شبیه به تعریف یک متد در کلاس است. با این تفاوت که:
  • سازنده هم نام کلاس است.
  • برای سازنده نوع خروجی در نظر گرفته نمی‌شود.

در مثال ما محدوده مجاز برای قاعده و ارتفاع مثلث بین ۱ تا ۱۰۰ است در حالی که سازنده پیش فرض مقدار صفر را برای آنها تنظیم خواهد نمود. پس برای اینکه مطمئن شویم اشیاء مثلث ساخته شده از این کلاس در همان بدو تولید دارای قاعده و ارتفاع معتبری هستند سازنده زیر را به صورت صریح در کلاس تعریف می‌کنیم تا جایگزین سازنده پیش فرضی شود که کامپایلر خواهد ساخت و به جای آن فراخوانی گردد.
public Triangle()
{
    _height = _baseLength = 1;
}
در این سازنده مقدار ۱ را برای متغیر خصوصی پشت (backing field یا backing store) هر یک از دو ویژگی قاعده و ارتفاع تنظیم نموده ایم.

اجرای سازنده

همانطور که گفته شد سازنده اضافه شده به کلاس جایگزین سازنده پیش فرض کامپایلر شده و در هنگام ایجاد یک شیء جدید از کلاس مثلث توسط عملگر new اجرا می‌شود. برای بررسی اجرا شدن سازنده به سادگی می‌توان کدی مشابه مثال زیر را نوشت.
Triangle triangle = new Triangle();
 
Console.WriteLine(triangle.Height);
Console.WriteLine(triangle.BaseLength);
Console.WriteLine(triangle.Area);
کد بالا مقدار ۱ را برای قاعده و ارتفاع و مقدار ۰.۵ را برای مساحت چاپ می‌نماید. بنابراین مشخص است که سازنده اجرا شده و مقادیر مناسب را برای شیء تنظیم نموده به طوری که شیء از بدو تولید در وضعیت مناسبی است.

سازنده‌های پارامتر دار

در مثال قبل یک سازنده بدون پارامتر را به کلاس اضافه کردیم. این سازنده تنها مقادیر پیش فرض مناسبی را تنظیم می‌کند. بدیهی است پس از ایجاد شیء در صورت نیاز می‌توان مقادیر مورد نظر دیگر را برای قاعده و ارتفاع تنظیم نمود. اما برای اینکه سازنده بهتر بتواند فرآیند وهله سازی را کنترل نماید می‌توان پارامترهایی را به آن افزود. افزودن پارامتر به سازنده مانند افزودن پارامتر به متدهای دیگر صورت می‌گیرد. در مثال زیر سازنده دیگری تعریف می‌کنیم که دارای دو پارامتر است. یکی قاعده و دیگری ارتفاع. به این ترتیب در حین فرآیند وهله سازی می‌توان مقادیر مورد نظر را منتسب نمود.
public Triangle(int height, int baseLength)
{
    Height = height;
    BaseLength = baseLength;
}
با توجه به اینکه مقادیر ارسالی به این سازنده توسط کد مشتری در نظر گرفته می‌شود و ممکن است در محدوده مجاز نباشد، به جای انتساب مستقیم این مقادیر به فیلد خصوصی پشت ویژگی قاعده و ارتفاع یعنی baseLength_ و height_ آنها را به پروپرتی‌ها منتسب کردیم تا قانون اعتبارسنجی موجود در اکسسور set پروپرتی‌ها از انتساب مقادیر غیر مجاز جلوگیری کند.
سازنده اخیر را می‌توان به صورت زیر با استفاده از عملگر new و فراهم کردن آرگومان‌های مورد نظر مورد استفاده قرار داد.
Triangle triangle = new Triangle(5, 8);
 
Console.WriteLine(triangle.Height);
Console.WriteLine(triangle.BaseLength);
Console.WriteLine(triangle.Area);
مقادیر چاپ شده برابر ۵ برای ارتفاع، ۸ برای قاعده و ۲۰ برای مساحت خواهد بود که نشان از اجرای صحیح سازنده دارد.
در مطالب بالا چندین بار از سازنده‌ها صحبت کردیم و گفتیم سازنده دیگری به کلاس اضافه می‌کنیم. این دو نکته را به خاطر داشته باشید:
  • یک کلاس می‌تواند دارای چندین سازنده باشد که بر اساس آرگومان‌های فراهم شده هنگام وهله سازی، سازنده مورد نظر انتخاب و اجرا می‌شود.
  • الزامی به تعریف یک سازنده پیش فرض (به معنای بدون پارامتر) نیست. یعنی یک کلاس می‌تواند هیچ سازنده بی پارامتری نداشته باشد.

در بخش‌های بعدی مطالب بیشتری در مورد سازنده‌ها و سایر اعضای کلاس خواهید آموخت.

 
نظرات مطالب
ایجاد یک Repository در پروژه برای دستورات EF
با سلام من یک  معماری طراحی کردم به شکل زیر
ابتدا یک اینترفیس به شکل زیر دارم
using System;
using System.Collections;
using System.Linq;

namespace Framework.Model
{
    public interface IContext
    {
        T Get<T>(Func<T, bool> prediction) where T : class;
        IEnumerable List<T>(Func<T, bool> prediction) where T : class;
        void Insert<T>(T entity) where T : class;
        int Save();
    }
}
بعد یک کلاس ازش مشتق شده
using System;
using System.Collections;
using System.Collections.Generic;
using System.Data;
using System.Data.Entity;
using System.Linq;
using System.Text;

namespace Framework.Model
{
    public class Context : IContext
    {
        private readonly DbContext _dbContext;

        public Context(DbContext context)
        {
            _dbContext = context;
        }

        public T Get<T>(Func<T,bool> prediction) where T : class
        {
            var dbSet = _dbContext.Set<T>();
            if (dbSet!= null)
                return dbSet.Single(prediction);

            throw new Exception();
        }

        public void Insert<T>(T entity) where T : class
        {
            var dbSet = _dbContext.Set<T>();
            if (dbSet != null)
            {
                _dbContext.Entry(entity).State = EntityState.Added;
            }
        }

        public int Save()
        {
            return _dbContext.SaveChanges();
        }


        IEnumerable IContext.List<T>(Func<T, bool> prediction)
        {
            var dbSet = _dbContext.Set<T>();
            if (dbSet != null)
                return dbSet.Where(prediction).ToList();

            throw new Exception();
        }
    }
}
سپس یک کلاش context دارم که مستقیما از dbcontext مشتق شده
using System.Data.Entity;
using DataModel;

namespace Model
{
    public class EFContext : DbContext
    {
        public EFContext(string db): base(db)
        {

        }

        public DbSet<Product> Products { get; set; }
    }
}
و سپس کلاس دارم که اومده پیاده سازی کرده context که خودم ساختمو 
using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Linq;
using System.Text;

namespace Model
{
    public class Context : Framework.Model.Context
    {
        public Context(string db): base(new EFContext(db))
        {
            
        }
    }
}
در پروژه دیگری اومدم یک کلاس context جدید ساختم 
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Biz
{
    public class Context : Model.Context
    {
        public Context(string db) : base(db)
        {

        }
    }
}
و در کنترلر هم به این شکل ازش استفاده کردم
using System.Web.Mvc;
using Framework.Model;

namespace ProductionRepository.Controllers
{
    public class BaseController : Controller
    {
        public IContext DataContext { get; set; }

        public BaseController()
        {
            DataContext = new Biz.Context(System.Configuration.ConfigurationManager.ConnectionStrings["Database"].ConnectionString);
        }
    }
}
using System.Web.Mvc;
using DataModel;
using System.Collections.Generic;

namespace ProductionRepository.Controllers
{
    public class ProductController : BaseController
    {
        public ActionResult Index()
        {
            var x = DataContext.List<Product>(s => s.Name != null);
            return View(x);
        }

    }
}
و این هم تست 
using NUnit.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Web.Mvc;

namespace TestUnit
{
    [TestFixture]
    public class Test
    {
        [Test]
        public void IndexShouldListProduct()
        {
            var repo = new Moq.Mock<Framework.Model.IContext>();
            var products = new List<DataModel.Product>();
            products.Add(new DataModel.Product { Id = 1, Name = "asdasdasd" });
            products.Add(new DataModel.Product { Id = 2, Name = "adaqwe" });
            products.Add(new DataModel.Product { Id = 4, Name = "qewqw" });
            products.Add(new DataModel.Product { Id = 5, Name = "qwe" });
            repo.Setup(x => x.List<DataModel.Product>(p => p.Name != null)).Returns(products.AsEnumerable());
            var controller = new ProductionRepository.Controllers.ProductController();
            controller.DataContext = repo.Object;
            var result = controller.Index() as ViewResult;
            var model = result.Model as List<DataModel.Product>;
            Assert.AreEqual(4, model.Count);
            Assert.AreEqual("", result.ViewName);

        }
    }
}
نظرتون چیه آقای نصیری
مطالب
امن سازی برنامه‌های ASP.NET Core توسط IdentityServer 4x - قسمت چهارم - نصب و راه اندازی IdentityServer
معرفی IdentityServer 4


اگر استاندارد OpenID Connect را بررسی کنیم، از مجموعه‌ای از دستورات و رهنمودها تشکیل شده‌است. بنابراین نیاز به کامپوننتی داریم که این استاندارد را پیاده سازی کرده باشد تا بتوان بر اساس آن یک Identity Provider را تشکیل داد و پیاده سازی مباحثی که در قسمت قبل بررسی شدند مانند توکن‌ها، Flow، انواع کلاینت‌ها، انواع Endpoints و غیره چیزی نیستند که به سادگی قابل انجام باشند. اینجا است که IdentityServer 4، به عنوان یک فریم ورک پیاده سازی کننده‌ی استانداردهای OAuth 2 و OpenID Connect مخصوص ASP.NET Core ارائه شده‌است. این فریم ورک توسط OpenID Foundation تائید شده و داری مجوز رسمی از آن است. همچنین جزئی از NET Foundation. نیز می‌باشد. به علاوه باید دقت داشت که این فریم ورک کاملا سورس باز است.


نصب و راه اندازی IdentityServer 4

همان مثال «قسمت دوم - ایجاد ساختار اولیه‌ی مثال این سری» را در نظر بگیرید. داخل آن پوشه‌های جدید src\IDP\DNT.IDP را ایجاد می‌کنیم.


نام دلخواه DNT.IDP، به پوشه‌ی جدیدی اشاره می‌کند که قصد داریم IDP خود را در آن برپا کنیم. نام آن را نیز در ادامه‌ی نام‌های پروژه‌های قبلی که با ImageGallery شروع شده‌اند نیز انتخاب نکرده‌ایم؛ از این جهت که یک IDP را قرار است برای بیش از یک برنامه‌ی کلاینت مورد استفاده قرار دهیم. برای مثال می‌توانید از نام شرکت خود برای نامگذاری این IDP استفاده کنید.

اکنون از طریق خط فرمان به پوشه‌ی src\IDP\DNT.IDP وارد شده و دستور زیر را صادر کنید:
dotnet new web
این دستور، یک پروژه‌ی جدیدی را از نوع «ASP.NET Core Empty»، در این پوشه، بر اساس آخرین نگارش SDK نصب شده‌ی بر روی سیستم شما، ایجاد می‌کند. از این جهت نوع پروژه خالی درنظر گرفته شده‌است که قرار است توسط اجزای IdentityServer 4 به مرور تکمیل شود.

اولین کاری را که در اینجا انجام خواهیم داد، مراجعه به فایل Properties\launchSettings.json آن و تغییر شماره پورت‌های پیش‌فرض آن است تا با سایر پروژه‌های وبی که تاکنون ایجاد کرده‌ایم، تداخل نکند. برای مثال در اینجا شماره پورت SSL آن‌را به 6001 تغییر داده‌ایم.

اکنون نوبت به افزودن میان‌افزار IdentityServer 4 به پروژه‌ی خالی وب است. اینکار را نیز توسط اجرای دستور زیر در پوشه‌ی src\IDP\DNT.IDP انجام می‌دهیم:
 dotnet add package IdentityServer4

در ادامه نیاز است این میان‌افزار جدید را معرفی و تنظیم کرد. به همین جهت فایل Startup.cs پروژه‌ی خالی وب را گشوده و به صورت زیر تکمیل می‌کنیم:
    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddMvc();

            services.AddIdentityServer()
                .AddDeveloperSigningCredential();
        }
- متد الحاقی AddIdentityServer، کار ثبت و معرفی سرویس‌های توکار IdentityServer را به سرویس توکار تزریق وابستگی‌های ASP.NET Core انجام می‌دهد.
- متد الحاقی AddDeveloperSigningCredential کار تنظیمات کلید امضای دیجیتال توکن‌ها را انجام می‌دهد. در نگارش‌های قبلی IdentityServer، اینکار با معرفی یک مجوز امضاء کردن توکن‌ها انجام می‌شد. اما در این نگارش دیگر نیازی به آن نیست. در طول توسعه‌ی برنامه می‌توان از نگارش Developer این مجوز استفاده کرد. البته در حین توزیع برنامه به محیط ارائه‌ی نهایی، باید به یک مجوز واقعی تغییر پیدا کند.


تعریف کاربران، منابع و کلاینت‌ها

مرحله‌ی بعدی تنظیمات میان‌افزار IdentityServer4، تعریف کاربران، منابع و کلاینت‌های این IDP است. به همین جهت یک کلاس جدید را به نام Config، در ریشه‌ی پروژه ایجاد و به صورت زیر تکمیل می‌کنیم:
using System.Collections.Generic;
using System.Security.Claims;
using IdentityServer4.Models;
using IdentityServer4.Test;

namespace DNT.IDP
{
    public static class Config
    {
        // test users
        public static List<TestUser> GetUsers()
        {
            return new List<TestUser>
            {
                new TestUser
                {
                    SubjectId = "d860efca-22d9-47fd-8249-791ba61b07c7",
                    Username = "User 1",
                    Password = "password",

                    Claims = new List<Claim>
                    {
                        new Claim("given_name", "Vahid"),
                        new Claim("family_name", "N"),
                    }
                },
                new TestUser
                {
                    SubjectId = "b7539694-97e7-4dfe-84da-b4256e1ff5c7",
                    Username = "User 2",
                    Password = "password",

                    Claims = new List<Claim>
                    {
                        new Claim("given_name", "User 2"),
                        new Claim("family_name", "Test"),
                    }
                }
            };
        }

        // identity-related resources (scopes)
        public static IEnumerable<IdentityResource> GetIdentityResources()
        {
            return new List<IdentityResource>
            {
                new IdentityResources.OpenId(),
                new IdentityResources.Profile()
            };
        }

        public static IEnumerable<Client> GetClients()
        {
            return new List<Client>();
        }
    }
}
توضیحات:
- این کلاس استاتیک، اطلاعاتی درون حافظه‌ای را برای تکمیل دموی جاری ارائه می‌دهد.

- ابتدا در متد GetUsers، تعدادی کاربر آزمایشی اضافه شده‌اند. کلاس TestUser در فضای نام IdentityServer4.Test قرار دارد. در کلاس TestUser، خاصیت SubjectId، بیانگر Id منحصربفرد هر کاربر در کل این IDP است. سپس نام کاربری، کلمه‌ی عبور و تعدادی Claim برای هر کاربر تعریف شده‌اند که بیانگر اطلاعاتی اضافی در مورد هر کدام از آن‌ها هستند. برای مثال نام و نام خانوادگی جزو خواص کلاس TestUser نیستند؛ اما منعی هم برای تعریف آن‌ها وجود ندارد. اینگونه اطلاعات اضافی را می‌توان توسط Claims به سیستم اضافه کرد.

- بازگشت Claims توسط یک IDP مرتبط است به مفهوم Scopes. برای این منظور متد دیگری به نام GetIdentityResources تعریف شده‌است تا لیستی از IdentityResource‌ها را بازگشت دهد که در فضای نام IdentityServer4.Models قرار دارد. هر IdentityResource، به یک Scope که سبب دسترسی به اطلاعات Identity کاربران می‌شود، نگاشت خواهد شد. در اینجا چون از پروتکل OpenID Connect استفاده می‌کنیم، ذکر IdentityResources.OpenId اجباری است. به این ترتیب مطمئن خواهیم شد که SubjectId به سمت برنامه‌ی کلاینت بازگشت داده می‌شود. برای بازگشت Claims نیز باید IdentityResources.Profile را به عنوان یک Scope دیگری مشخص کرد که در متد GetIdentityResources مشخص شده‌است.

- در آخر نیاز است کلاینت‌های این IDP را نیز مشخص کنیم (در مورد مفهوم Clients در قسمت قبل بیشتر توضیح داده شد) که اینکار در متد GetClients انجام می‌شود. فعلا یک لیست خالی را بازگشت می‌دهیم و آن‌را در قسمت‌های بعدی تکمیل خواهیم کرد.


افزودن کاربران، منابع و کلاینت‌ها به سیستم

پس از تعریف و تکمیل کلاس Config، برای معرفی آن به IDP، به کلاس آغازین برنامه مراجعه کرده و آن‌را به صورت زیر تکمیل می‌کنیم:
    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddMvc();

            services.AddIdentityServer()
             .AddDeveloperSigningCredential()
             .AddTestUsers(Config.GetUsers())
             .AddInMemoryIdentityResources(Config.GetIdentityResources())
             .AddInMemoryClients(Config.GetClients());
        }
در اینجا لیست کاربران و اطلاعات آن‌ها توسط متد AddTestUsers، لیست منابع و Scopes توسط متد AddInMemoryIdentityResources و لیست کلاینت‌ها توسط متد AddInMemoryClients به تنظیمات IdentityServer اضافه شده‌اند.


افزودن میان افزار IdentityServer به برنامه

پس از انجام تنظیمات مقدماتی سرویس‌های برنامه، اکنون نوبت به افزودن میان‌افزار IdentityServer است که در کلاس آغازین برنامه به صورت زیر تعریف می‌شود:
        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }

            app.UseIdentityServer();
            app.UseStaticFiles();
            app.UseMvcWithDefaultRoute();
        }

آزمایش IDP

اکنون برای آزمایش IDP، به پوشه‌ی src\IDP\DNT.IDP وارد شده و دستور dotnet run را اجرا کنید:


همانطور که ملاحظه می‌کنید، برنامه‌ی IDP بر روی پورت 6001 قابل دسترسی است. برای آزمایش Web API آن، آدرس discovery endpoint این IDP را به صورت زیر در مرورگر وارد کنید:
 https://localhost:6001/.well-known/openid-configuration


در این تصویر، مفاهیمی را که در قسمت قبل بررسی کردیم مانند authorization_endpoint ،token_endpoint و غیره، مشاهده می‌کنید.


افزودن UI به IdentityServer

تا اینجا میان‌افزار IdentityServer را نصب و راه اندازی کردیم. در نگارش‌های قبلی آن، UI به صورت پیش‌فرض جزئی از این سیستم بود. در این نگارش آن‌را می‌توان به صورت جداگانه دریافت و به برنامه اضافه کرد. برای این منظور به آدرس IdentityServer4.Quickstart.UI مراجعه کرده و همانطور که در readme آن ذکر شده‌است می‌توان از یکی از دستورات زیر برای افزودن آن به پروژه‌ی IDP استفاده کرد:
الف) در ویندوز از طریق کنسول پاورشل به پوشه‌ی src\IDP\DNT.IDP وارد شده و سپس دستور زیر را وارد کنید:
iex ((New-Object System.Net.WebClient).DownloadString('https://raw.githubusercontent.com/IdentityServer/IdentityServer4.Quickstart.UI/release/get.ps1'))
ب) و یا درmacOS و یا Linux، دستور زیر را اجرا کنید:
\curl -L https://raw.githubusercontent.com/IdentityServer/IdentityServer4.Quickstart.UI/release/get.sh | bash

یک نکته: در ویندوز اگر در نوار آدرس هر پوشه، عبارت cmd را وارد و enter کنید، کنسول خط فرمان ویندوز در همان پوشه باز خواهد شد. همچنین در اینجا از ورود عبارت powershell هم پشتیبانی می‌شود:


بنابراین در نوار آدرس پوشه‌ی src\IDP\DNT.IDP، عبارت powershell را وارد کرده و سپس enter کنید. پس از آن دستور الف را وارد (copy/paste) و اجرا کنید.


به این ترتیب فایل‌های IdentityServer4.Quickstart.UI به پروژه‌ی IDP جاری اضافه می‌شوند.
- پس از آن اگر به پوشه‌ی Views مراجعه کنید، برای نمونه ذیل پوشه‌ی Account آن، Viewهای ورود و خروج به سیستم قابل مشاهده هستند.
- در پوشه‌ی Quickstart آن، کدهای کامل کنترلرهای متناظر با این Viewها قرار دارند.
بنابراین اگر نیاز به سفارشی سازی این Viewها را داشته باشید، کدهای کامل کنترلرها و Viewهای آن هم اکنون در پروژه‌ی IDP جاری در دسترس هستند.

نکته‌ی مهم: این UI اضافه شده، یک برنامه‌ی ASP.NET Core MVC است. به همین جهت در انتهای متد Configure، ذکر میان افزارهای UseStaticFiles و همچنین UseMvcWithDefaultRoute انجام شدند.

اکنون اگر برنامه‌ی IDP را مجددا با دستور dotnet run اجرا کنیم، تصویر زیر را می‌توان در ریشه‌ی سایت، مشاهده کرد که برای مثال لینک discovery endpoint در همان سطر اول آن ذکر شده‌است:


همچنین همانطور که در قسمت قبل نیز ذکر شد، یک IDP حتما باید از طریق پروتکل HTTPS در دسترس قرار گیرد که در پروژه‌های ASP.NET Core 2.1 این حالت، جزو تنظیمات پیش‌فرض است.


کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید.
مطالب
استفاده از نگارش سوم Google Analytics API در سرویس‌های ویندوز یا برنامه‌های وب
در زمان نگارش این مطلب، آخرین نگارش API مخصوص Google Analytics، نگارش سوم آن است و ... کار کردن با آن دارای مراحل خاصی است که حتما باید رعایت شوند. در غیر اینصورت عملا در یک برنامه‌ی وب یا سرویس ویندوز قابل اجرا نخواهند بود. زیرا در حالت متداول کار با API مخصوص Google Analytics، ابتدا یک صفحه‌ی لاگین به Gmail باز می‌شود که باید به صورت اجباری، مراحل آن را انجام داد تا مشخصات تائید شده‌ی اکانت در حال استفاده‌ی از API، در پوشه‌ی AppData ویندوز برای استفاده‌های بعدی ذخیره شود. این مورد برای یک برنامه‌ی دسکتاپ معمولی مشکل ساز نیست؛ زیرا کاربر برنامه، به سادگی می‌تواند صفحه‌ی مرورگری را که باز شده‌است، دنبال کرده و به اکانت گوگل خود وارد شود. اما این مراحل را نمی‌توان در یک برنامه‌ی وب یا سرویس ویندوز پیگیری کرد، زیرا عموما امکان لاگین از راه دور به سرور و مدیریت صفحه‌ی لاگین به Gmail وجود ندارد یا بهتر است عنوان شود، بی‌معنا است. برای حل این مشکل، گوگل راه حل دیگری را تحت عنوان اکانت‌های سرویس، ارائه داده است که پس از ایجاد آن، یک یک فایل X509 Certificate برای اعتبارسنجی سرویس، در اختیار برنامه نویس قرار می‌گیرد تا بدون نیاز به لاگین دستی به Gmail، بتواند از API گوگل استفاده کند. در ادامه نحوه‌ی فعال سازی این قابلیت و استفاده از آن‌را بررسی خواهیم کرد.


ثبت برنامه‌ی خود در گوگل و انجام تنظیمات آن

اولین کاری که برای استفاده از نگارش سوم Google Analytics API باید صورت گیرد، ثبت برنامه‌ی خود در Google Developer Console است. برای این منظور ابتدا به آدرس ذیل وارد شوید:
سپس بر روی دکمه‌ی Create project کلیک کنید. نام دلخواهی را وارد کرده و در ادامه بر روی دکمه‌ی Create کلیک نمائید تا پروفایل این پروژه ایجاد شود.



تنها نکته‌ی مهم این قسمت، بخاطر سپردن نام پروژه است. زیرا از آن جهت اتصال به API گوگل استفاده خواهد شد.
پس از ایجاد پروژه، به صفحه‌ی آن وارد شوید و از منوی سمت چپ صفحه، گزینه‌ی Credentials را انتخاب کنید. در ادامه در صفحه‌ی باز شده، بر روی دکمه‌ی Create new client id کلیک نمائید.


در صفحه‌ی باز شده، گزینه‌ی Service account را انتخاب کنید. اگر سایر گزینه‌ها را انتخاب نمائید، کاربری که قرار است از API استفاده کند، باید بتواند توسط مرورگر نصب شده‌ی بر روی کامپیوتر اجرا کننده‌ی برنامه، یکبار به گوگل لاگین نماید که این مورد مطلوب برنامه‌های وب و همچنین سرویس‌ها نیست.


در اینجا ابتدا یک فایل مجوز p12 را به صورت خودکار دریافت خواهید کرد و همچنین پس از ایجاد client id، نیاز است، ایمیل آن‌را جایی یادداشت نمائید:


از این ایمیل و همچنین فایل p12 ارائه شده، جهت لاگین به سرور استفاده خواهد شد.
همچنین نیاز است تا به برگه‌ی APIs پروژه‌ی ایجاد شده رجوع کرد و گزینه‌ی Analytics API آن‌را فعال نمود:


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


دادن دسترسی به Client ID ثبت شده در برنامه‌ی Google Analytics

پس از اینکه Client ID سرویس خود را ثبت کردید، نیاز است به اکانت Google Analytics خود وارد شوید. سپس در منوی آن، گزینه‌ی Admin را پیدا کرده و به آن قسمت، وارد شوید:


در ادامه به گزینه‌ی User management آن وارد شده و به ایمیل Client ID ایجاد شده در قسمت قبل، دسترسی خواندن و آنالیز را اعطاء کنید:


در صورت عدم رعایت این مساله، کلاینت API، قادر به دسترسی به Google Analytics نخواهد بود.


استفاده از نگارش سوم Google Analytics API در دات نت

قسمت مهم کار، تنظیمات فوق است که در صورت عدم رعایت آن‌ها، شاید نصف روزی را مشغول به دیباگ برنامه شوید. در ادامه نیاز است پیشنیازهای دسترسی به نگارش سوم Google Analytics API را نصب کنیم. برای این منظور، سه بسته‌ی نیوگت ذیل را توسط کنسول پاورشل نیوگت، به برنامه اضافه کنید:
 PM> Install-Package Google.Apis
PM> Install-Package Google.Apis.auth
PM> Install-Package Google.Apis.Analytics.v3
پس از نصب، کلاس GoogleAnalyticsApiV3 زیر، جزئیات دسترسی به Google Analytics API را کپسوله می‌کند:
using System;
using System.Linq;
using System.Security.Cryptography.X509Certificates;
using Google.Apis.Analytics.v3;
using Google.Apis.Analytics.v3.Data;
using Google.Apis.Auth.OAuth2;
using Google.Apis.Services;

namespace GoogleAnalyticsAPIv3Tests
{
    public class AnalyticsQueryParameters
    {
        public DateTime Start { set; get; }
        public DateTime End { set; get; } 
        public string Dimensions { set; get; } 
        public string Filters { set; get; }
        public string Metrics { set; get; }
    }

    public class AnalyticsAuthentication
    {
        public Uri SiteUrl { set; get; }
        public string ApplicationName { set; get; }
        public string ServiceAccountEmail { set; get; }
        public string KeyFilePath { set; get; }
        public string KeyFilePassword { set; get; }

        public AnalyticsAuthentication()
        {
            KeyFilePassword = "notasecret";
        }
    }

    public class GoogleAnalyticsApiV3
    {
        public AnalyticsAuthentication Authentication { set; get; }
        public AnalyticsQueryParameters QueryParameters { set; get; }

        public GaData GetData()
        {
            var service = createAnalyticsService();
            var profile = getProfile(service);
            var query = service.Data.Ga.Get("ga:" + profile.Id,
                                QueryParameters.Start.ToString("yyyy-MM-dd"),
                                QueryParameters.End.ToString("yyyy-MM-dd"),
                                QueryParameters.Metrics);
            query.Dimensions = QueryParameters.Dimensions;
            query.Filters = QueryParameters.Filters;
            query.SamplingLevel = DataResource.GaResource.GetRequest.SamplingLevelEnum.HIGHERPRECISION;
            return query.Execute();
        }

        private AnalyticsService createAnalyticsService()
        {
            var certificate = new X509Certificate2(Authentication.KeyFilePath, Authentication.KeyFilePassword, X509KeyStorageFlags.Exportable);
            var credential = new ServiceAccountCredential(
                new ServiceAccountCredential.Initializer(Authentication.ServiceAccountEmail)
                {
                    Scopes = new[] { AnalyticsService.Scope.AnalyticsReadonly }
                }.FromCertificate(certificate));

            return new AnalyticsService(new BaseClientService.Initializer
            {
                HttpClientInitializer = credential,
                ApplicationName = Authentication.ApplicationName
            });
        }

        private Profile getProfile(AnalyticsService service)
        {
            var accountListRequest = service.Management.Accounts.List();
            var accountList = accountListRequest.Execute();
            var site = Authentication.SiteUrl.Host.ToLowerInvariant();
            var account = accountList.Items.FirstOrDefault(x => x.Name.ToLowerInvariant().Contains(site));
            var webPropertyListRequest = service.Management.Webproperties.List(account.Id);
            var webPropertyList = webPropertyListRequest.Execute();
            var sitePropertyList = webPropertyList.Items.FirstOrDefault(a => a.Name.ToLowerInvariant().Contains(site));
            var profileListRequest = service.Management.Profiles.List(account.Id, sitePropertyList.Id);
            var profileList = profileListRequest.Execute();
            return profileList.Items.FirstOrDefault(a => a.Name.ToLowerInvariant().Contains(site));
        }
    }
}
در اینجا در ابتدا بر اساس فایل p12 ایی که از گوگل دریافت شد، یک X509Certificate2 ایجاد می‌شود. پسورد این فایل مساوی است با ثابت notasecret که در همان زمان تولید اکانت سرویس در گوگل، لحظه‌ای در صفحه نمایش داده خواهد شد. به کمک آن و همچنین ServiceAccountEmail ایمیلی که پیشتر به آن اشاره شد، می‌توان به AnalyticsService لاگین کرد. به این ترتیب به صورت خودکار می‌توان شماره پروفایل اکانت سایت خود را یافت و از آن در حین فراخوانی service.Data.Ga.Get استفاده کرد.


مثالی از نحوه استفاده از کلاس GoogleAnalyticsApiV3

در ادامه یک برنامه‌ی کنسول را ملاحظه می‌کنید که از کلاس GoogleAnalyticsApiV3 استفاده می‌کند:
using System;
using System.Collections.Generic;
using System.Linq;

namespace GoogleAnalyticsAPIv3Tests
{
    class Program
    {

        static void Main(string[] args)
        {
            var statistics = new GoogleAnalyticsApiV3
            {
                Authentication = new AnalyticsAuthentication
                {
                    ApplicationName = "My Project",
                    KeyFilePath = "811e1d9976cd516b55-privatekey.p12",
                    ServiceAccountEmail = "10152bng4j3mq@developer.gserviceaccount.com",
                    SiteUrl = new Uri("https://www.dntips.ir/")
                },
                QueryParameters = new AnalyticsQueryParameters
                {
                    Start = DateTime.Now.AddDays(-7),
                    End = DateTime.Now,
                    Dimensions = "ga:date",
                    Filters = null,
                    Metrics = "ga:users,ga:sessions,ga:pageviews"
                }
            }.GetData();


            foreach (var result in statistics.TotalsForAllResults)
            {
                Console.WriteLine(result.Key + " -> total:" + result.Value);
            }
            Console.WriteLine();

            foreach (var row in statistics.ColumnHeaders)
            {
                Console.Write(row.Name + "\t");
            }
            Console.WriteLine();

            foreach (var row in statistics.Rows)
            {
                var rowItems = (List<string>)row;
                Console.WriteLine(rowItems.Aggregate((s1, s2) => s1 + "\t" + s2));
            }

            Console.ReadLine();
        }
    }
}

چند نکته
ApplicationName همان نام پروژه‌ای است که ابتدای کار، در گوگل ایجاد کردیم.
KeyFilePath مسیر فایل مجوز p12 ایی است که گوگل در حین ایجاد اکانت سرویس، در اختیار ما قرار می‌دهد.
ServiceAccountEmail آدرس ایمیل اکانت سرویس است که در قسمت ادمین Google Analytics به آن دسترسی دادیم.
SiteUrl آدرس سایت شما است که هم اکنون در Google Analytics دارای یک اکانت و پروفایل ثبت شده‌است.
توسط AnalyticsQueryParameters می‌توان نحوه‌ی کوئری گرفتن از Google Analytics را مشخص کرد. تاریخ شروع و پایان گزارش گیری در آن مشخص هستند. در مورد پارامترهایی مانند Dimensions و Metrics بهتر است به مرجع کامل آن در گوگل مراجعه نمائید:
Dimensions & Metrics Reference

برای نمونه در مثال فوق، تعداد کاربران، سشن‌های آن و همچنین تعداد بار مشاهده‌ی صفحات، گزارشگیری می‌شود.


برای مطالعه بیشتر
Using Google APIs in Windows Store Apps
How To Use Google Analytics From C# With OAuth
Google Analytic’s API v3 with C#
.NET Library for Accessing and Querying Google Analytics V3 via Service Account
Google OAuth2 C#
مطالب
نوشتن آزمون‌های واحد به کمک کتابخانه‌ی Moq - قسمت پنجم - نکات و مباحث تکمیلی
پس از بررسی مباحث و نکات پایه‌ای کار با کتابخانه‌ی Moq، در این قسمت تعدادی از نکات تکمیلی آن‌را بررسی خواهیم کرد.


حالت‌های عملکرد کتابخانه‌ی Moq

کتابخانه‌ی Moq، دو حالت عملکرد را دارد: Strict Mode و Loose mode. زمانیکه یک Mock object را نمونه سازی می‌کنیم، به صورت پیش‌فرض کتابخانه‌ی Moq، یک Loose mock را ایجاد می‌کند. در این حالت این شیء، مقادیر پیش‌فرض خواص و اشیاء را بازگشت می‌دهد و استثنائی را صادر نمی‌کند. اگر این موارد مدنظر نیستند، می‌توان به حالت Strict آن رجوع کرد که روش تنظیم آن به صورت زیر است:
var mockIdentityVerifier = new Mock<IIdentityVerifier>(MockBehavior.Strict);
در این حالت اگر متد آزمون واحد را اجرا کنیم، با پیام زیر، با شکست مواجه خواهد شد:
Test method Loans.Tests.LoanApplicationProcessorShould.Accept threw exception:
Moq.MockException: IIdentityVerifier.Initialize() invocation failed with mock behavior Strict.
All invocations on the mock must have a corresponding setup.
در حالت Strict، تمام فراخوانی‌های شیء Mock شده باید دارای Setup باشند (نیازی به Setup تمام موارد نیست؛ فقط مواردی که در فراخوانی‌های آزمون واحد، مورد استفاده قرار می‌گیرند، حتما باید تنظیم شوند). برای نمونه در اینجا عنوان کرده‌است که در این آزمایش، تنظیمات متد Initialize انجام نشده‌است که با تعریف سطر زیر، این مشکل برطرف می‌شود:
mockIdentityVerifier.Setup(x => x.Initialize());

بنابراین هرچند کارکردن با حالت پیش‌فرض کتابخانه‌ی Moq ساده‌است، اما تنظیم حالت Strict سبب می‌شود تا تنظیمی را فراموش نکنیم و در نتیجه کیفیت آزمون واحد تهیه شده افزایش می‌یابد.


صدور استثناءها از طریق Mock objects

اگر در سیستم در حال آزمایش، قسمتی به بررسی خطاها اختصاص دارد، می‌توان توسط Mock objects استثناءهایی را تولید و به این ترتیب منطق بررسی خطاها را آزمایش کرد.
برای نمونه در متد Process کلاس LoanApplicationProcessor، یک try/catch را به قسمت CalculateScore اضافه می‌کنیم:
try
{
    _creditScorer.CalculateScore(application.Applicant.Name, application.Applicant.Address);
}
catch
{
    return application.IsAccepted;
}
زمانیکه کار فراخوانی متد CalculateScore صورت می‌گیرد، برای تنظیم آزمون واحد آن می‌توان از متد Throws، برای صدور یک استثناء استفاده کرد:
mockCreditScorer.Setup(x =>
                    x.CalculateScore(It.IsAny<string>(), It.IsAny<string>()))
                .Throws(new InvalidOperationException("Test Exception"));
صدور این استثناء سبب خواهد شد تا درخواست شخص، رد شود. بنابراین در آزمایش آن می‌توان این مساله را بررسی کرد و از رسیدن به این قسمت (رد شدن درخواست) اطمینان حاصل نمود:
Assert.IsFalse(application.IsAccepted);


صدور رخدادها از طریق Mock objects

فرض کنید یک EventArgs سفارشی را به صورت زیر تعریف:
using System;

namespace Loans.Models
{
    public class CreditScoreResultArgs : EventArgs
    {
        public int Score { get; set; }
    }
}
و سپس رخدادی را به نحو زیر به ICreditScorer اضافه کرده‌ایم:
public interface ICreditScorer
{
   event EventHandler<CreditScoreResultArgs> ResultAvailable;
برای اینکه یک Mock object سبب بروز رخداد ResultAvailable شود (به صورت دستی و دقیقا در سطری که مشخص می‌کنیم)، می‌توان به صورت زیر عمل کرد:
mockCreditScorer.Raise(x => x.ResultAvailable += null, new CreditScoreResultArgs());
ابتدا توسط متد Raise، رخ‌داد مدنظر را ذکر می‌کنیم و سپس یک نمونه‌ی EventArgs را به آن ارسال خواهیم کرد.
روش دیگر انجام اینکار به صورت زیر است:
mockCreditScorer.Setup(x =>
                x.CalculateScore(It.IsAny<string>(), It.IsAny<string>()))
                .Raises(x => x.ResultAvailable += null, new CreditScoreResultArgs());
در این حالت با فراخوانی متد CalculateScore، رخداد ResultAvailable به صورت خودکار صادر می‌شود.


معرفی Partial Mocks

در اغلب آزمون‌های واحدی که تا اینجا بررسی شدند، ابتدا یک Mock object را ایجاد و سپس وهله‌ای از سرویس مدنظر را توسط آن تهیه می‌کنیم. در ادامه تعدادی از متدهای این سرویس را مانند متد Process کلاس LoanApplicationProcessor، فراخوانی می‌کنیم. اینکار سبب اجرای فعالیتی در این سیستم شده و به همراه آن تعاملی با اشیاء Mock شده نیز صورت می‌گیرد. در نهایت حالت و یا نتیجه‌ای را دریافت می‌کنیم و آن‌را با حالت یا نتیجه‌ای که انتظار داریم، مقایسه خواهیم کرد. در این روش پس از پایان اجرای سیستم در حال اجرا، حالت و نتیجه‌ی نهایی حاصل از عملکرد آن، مورد بررسی قرار می‌گیرد. این بررسی‌ها را نیز بر روی اینترفیس‌ها انجام دادیم. اگر بجای اینترفیس‌ها از یک class استفاده شود، به آن partial mock گفته می‌شود. عموما مواردی را که آزمایش آن‌ها سخت است، با Partial mocks پیاده سازی می‌کنند؛ مانند کار با فایل سیستم، کار با قطعه کدهای نامعین مانند DateTime.Now، اعداد اتفاقی و یا Guidها.

در مثال زیر، شبیه به متد آزمون واحد Accept که تاکنون آن‌را بررسی کردیم، از اشیاء Mock شده استفاده شده‌است؛ با یک تفاوت: بجای اینترفیس IIdentityVerifier، از کلاس پیاده سازی کننده‌ی آن که در اینجا IdentityVerifierServiceGateway است، استفاده شده:
namespace Loans.Tests
{
    [TestClass]
    public class LoanApplicationProcessorShould
    {        
        [TestMethod]
        public void AcceptUsingPartialMock()
        {
            var product = new LoanProduct {Id = 99, ProductName = "Loan", InterestRate = 5.25m};
            var amount = new LoanAmount {CurrencyCode = "Rial", Principal = 2_000_000_0};
            var applicant =
                new Applicant {Id = 1, Name = "User 1", Age = 25, Address = "This place", Salary = 1_500_000_0};
            var application = new LoanApplication {Id = 42, Product = product, Amount = amount, Applicant = applicant};

            var mockIdentityVerifier = new Mock<IdentityVerifierServiceGateway>();

            mockIdentityVerifier.Setup(x => x.CallService(applicant.Name, applicant.Age, applicant.Address))
                .Returns(true);

            var mockCreditScorer = new Mock<ICreditScorer>();
            mockCreditScorer.Setup(x => x.ScoreResult.ScoreValue.Score).Returns(110_000);

            var sut = new LoanApplicationProcessor(mockIdentityVerifier.Object, mockCreditScorer.Object);
            sut.Process(application);

            Assert.IsTrue(application.IsAccepted);
        }
    }
}
در اینجا برای اینکه بتوانیم متد CallService را که private بوده، بررسی و تنظیم کنیم، آن‌را به public virtual تبدیل کرده‌ایم تا توسط Moq قابل دسترسی و همچنین قابل بازنویسی شود:
public virtual bool CallService(string applicantName, int applicantAge, string applicantAddress)


تبدیل DateTime.Now به یک مقدار ثابت قابل آزمایش توسط Partial Mocks

در کلاس IdentityVerifierServiceGateway، یک چنین کدی را داریم که از DateTime.Now نامشخص استفاده می‌کند و آزمون واحد نوشتن برای آن مشکل است؛ چون DateTime.Now در هربار که آزمایش اجرا می‌شود، تغییر می‌کند:
public bool Validate(string applicantName, int applicantAge, string applicantAddress)
{
    Connect();
    var isValidIdentity = CallService(applicantName, applicantAge, applicantAddress);
    LastCheckTime = DateTime.Now;
    Disconnect();

    return isValidIdentity;
}
برای بالابردن قابلیت آزمون نویسی این کلاس، آن‌را به صورت زیر Refactor می‌کنیم تا DateTime.Now را به صورت یک متد public virtual دریافت کند:
public bool Validate(string applicantName, int applicantAge, string applicantAddress)
{
    Connect();
    var isValidIdentity = CallService(applicantName, applicantAge, applicantAddress);
    LastCheckTime = GetCurrentTime();
    Disconnect();

    return isValidIdentity;
}

public virtual DateTime GetCurrentTime()
{
    return DateTime.Now;
}
اکنون آزمون واحد نویسی برای این کلاس توسط Mock objects بسیار ساده‌است:
var expectedTime = new DateTime(2000, 1, 1);
mockIdentityVerifier.Setup(x => x.GetCurrentTime())
    .Returns(expectedTime);
// ...
Assert.AreEqual(expectedTime, mockIdentityVerifier.Object.LastCheckTime);
در اینجا خروجی متد GetCurrentTime بر روی Mock object تهیه شده، به یک مقدار ثابت تنظیم شده‌است که با هر بار اجرای آزمایش در زمان‌های مختلف، تغییری نمی‌کند و وابسته‌ی به DateTime.Now نامشخص، نیست.


استفاده از متدهای protected بجای استفاده از متدهای public virtual در Partial Mocks

همانطور که مشاهده کردید، برای کار با Partial Mocks نیاز است متدهای معرفی شده، از نوع public virtual باشند. برای نمونه حتی مجبور شدیم یک متد private را نیز public کنیم. اگر علاقمند به این نوع تغییرات نیستید، می‌توان بجای public کردن متدهای private، آن‌ها را protected تعریف کرد. به همین جهت دو متدی را که تاکنون public virtual تعریف کردیم، تبدیل به protected virtual می‌کنیم.
پس از آن در کلاسی که آزمون‌های واحد را تهیه کردیم، ابتدا using Moq.Protected را ذکر می‌کنیم تا بتوانیم به قابلیت‌های ویژه‌ی کار با متدهای Protected دسترسی پیدا کنیم.
سپس روش تنظیم این نوع متدهای protected، چون دسترسی مستقیمی به آن‌ها وجود ندارد، به صورت زیر، با ذکر نام رشته‌ای آن‌ها تغییر می‌کند:
mockIdentityVerifier.Protected().Setup<bool>(
        "CallService",applicant.Name, applicant.Age, applicant.Address)
    .Returns(true);

var expectedTime = new DateTime(2000, 1, 1);
mockIdentityVerifier.Protected().Setup<DateTime>("GetCurrentTime")
    .Returns(expectedTime);
ابتدا متد Protected شیء Mock شده ذکر می‌شود و پس از آن متد Setup باید دقیقا نوع بازگشتی متد در حال تنظیم را ذکر کند؛ چون دیگر دسترسی strongly typed ای به آن نداریم. پس ا‌ز آن، لیست پارامترهای متد، ذکر می‌شوند.

روش دیگری نیز برای تعریف متدهای protected وجود دارد که اینبار strongly typed است. بالای متد آزمون واحد، اینترفیس private زیر را تعریف می‌کنیم:
interface IIdentityVerifierServiceGatewayProtectedMembers
{
   DateTime GetCurrentTime();
   bool CallService(string applicantName, int applicantAge, string applicantAddress);
}
که در آن متدهای تعریف شده، با متدهای protected در حال بررسی، امضای یکسانی دارند (و همواره با هر تغییری در برنامه نیز باید این وضعیت حفظ شود). در ادامه تعاریف تنظیمات این متدها به صورت strongly typed زیر قابل انجام است:
mockIdentityVerifier.Protected()
    .As<IIdentityVerifierServiceGatewayProtectedMembers>()
    .Setup(x => x.CallService(It.IsAny<string>(),
        It.IsAny<int>(),
        It.IsAny<string>()))
    .Returns(true);

var expectedTime = new DateTime(2000, 1, 1);
mockIdentityVerifier.Protected()
    .As<IIdentityVerifierServiceGatewayProtectedMembers>()
    .Setup(x => x.GetCurrentTime())
    .Returns(expectedTime);


معرفی روش دیگری بجای استفاده از متدهای protected

اگر در کدهای خود نیاز به استفاده‌ی بیش از حد از متدهای protected را مشاهده کردید، این مورد می‌توان نشانه‌ی امکان Refactoring این قسمت از کدها به سرویس‌هایی مجزا باشند. برای مثال می‌توان یک اینترفیس INowProvider را به صورت زیر تعریف کرد:
using System;

namespace Loans.Services.Contracts
{
    public interface INowProvider
    {
        DateTime GetNow();
    }
}
و سپس آن‌را به سازنده‌ی کلاس IdentityVerifierServiceGateway تزریق کرد:
    public class IdentityVerifierServiceGateway : IIdentityVerifier
    {
        private readonly INowProvider _nowProvider;
        
        public DateTime LastCheckTime { get; private set; }

        public IdentityVerifierServiceGateway(INowProvider nowProvider)
        {
            _nowProvider = nowProvider;
        }
 و متد GetCurrentTime را حذف و آن‌را با متد GetNow این سرویس جایگزین نمود:
        public bool Validate(string applicantName, int applicantAge, string applicantAddress)
        {
            Connect();
            var isValidIdentity = CallService(applicantName, applicantAge, applicantAddress);
            LastCheckTime = _nowProvider.GetNow();
            // ...
 به این ترتیب نیاز به تنظیم متد protected بازگشت زمان، حذف شده و می‌توان از این سرویس جدید استفاده کرد:
var mockNowProvider = new Mock<INowProvider>();
mockNowProvider.Setup(x => x.GetNow()).Returns(expectedTime);

var mockIdentityVerifier =  new Mock<IdentityVerifierServiceGateway>(mockNowProvider.Object);


کدهای کامل این سری را از اینجا می‌توانید دریافت کنید: MoqSeries-05.zip