اشتراک‌ها
پیاده‌سازی فرآیند Impersonation در ASP.NET Core

Impersonation Process

Impersonation is when an admin user is logged in with the same privileges as a user, but without knowing their password or other credentials. I’ve used this in couple applications and it was invaluable for support cases and debugging user permissions. 

[Authorize(Roles = "Admin")] // <-- Make sure only admins can access this 
public async Task<IActionResult> ImpersonateUser(String userId)
{
    var currentUserId = User.GetUserId();

    var impersonatedUser = await _userManager.FindByIdAsync(userId);

    var userPrincipal = await _signInManager.CreateUserPrincipalAsync(impersonatedUser);

    userPrincipal.Identities.First().AddClaim(new Claim("OriginalUserId", currentUserId));
    userPrincipal.Identities.First().AddClaim(new Claim("IsImpersonating", "true"));

    // sign out the current user
    await _signInManager.SignOutAsync();

    await HttpContext.Authentication.SignInAsync(cookieOptions.ApplicationCookieAuthenticationScheme, userPrincipal);

    return RedirectToAction("Index", "Home");
}


پیاده‌سازی فرآیند Impersonation در ASP.NET Core
مطالب
MethodCallSerializer یا Serialize کردن فراخوانی متد

مدتی پیش نیاز پیدا کردم تا فراخوانی متدهایی را Serialize کرده و در مواقعی خاص، آن متدها را فراخوانی کنم که نتیجه‌ی آن را در زیر با هم می‌بینیم. 

در نظر بگیرید متدی داریم به شکل زیر: 

public class EmailSender
    {
        public void Send(string emailAddress)
        {
            Console.WriteLine($"an email was sent to {emailAddress}");
        }
    }

و می‌خواهیم نحوه فراخوانی این متد را Serialize کرده

new EmailSender().Send("eng.younos1986@gmail.com")

یعنی با چنین کدی، متد مورد نظر را Serialize کرده، Type ، Method.Name و Argument ‌های آن را بدست آورده و در دیتابیس ذخیره کنیم:

Serialize(() => new EmailSender().Send("eng.younos1986@gmail.com"));
که در ادامه برای Serialize کردن به صورت زیر عمل می‌کنیم:  
public void Serialize(Expression<Action> methodCall)
        {
            var callExpression = methodCall.Body as MethodCallExpression;

            var rawArguments = new object[callExpression.Arguments.Count];
            var i = 0;
            foreach (var argg in callExpression.Arguments)
            {
                rawArguments[i++] = Expression.Lambda(Expression.Convert(argg, argg.Type)).Compile().DynamicInvoke();
            }
            string typeName = string.Empty;
            var serializedArgumentsObject = rawArguments.ObjectToByteArray();              //todo: save this to db as method parameters  [Binary field]

            if (callExpression.Object != null)                                             //todo: save this to db as class name to be instanciated later {nvarchar field}
                typeName = callExpression.Object.Type.ToString();                          // instance methods
            else
                typeName = callExpression.Method.ReflectedType.ToString();                 // static methods

            var methodname = callExpression.Method.Name;                                   //todo: save this to db as method name to be called via reflection [nvarchar field]
            var deserializedArgumentsObject = serializedArgumentsObject.ByteArrayToObject();                                             //todo:  retrieve  serializedObject fro db and deserialize 
            var objInstance = GetInstance(typeName);                                       //todo:  retrieve  typeName fro db and deserialize 
            if (objInstance != null)
            {
                objInstance.GetType().GetMethod(methodname).Invoke(objInstance, (object[])deserializedArgumentsObject);
            }
        }  


برای اینکه بتوانیم نحوه فراخوانی متد را به صورت Lambda Expressions به متد Serialize بفرستیم، باید نوع پارارمتر آن از جنس <Expression<Action باشد. 
بعد با Cast کردن methodCall.Body به MethodCallExpression می‌توانیم آرگیومنت‌ها، نام متد و Type آن را بدست بیاوریم:



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

var rawArguments = new object[callExpression.Arguments.Count];
var i = 0;
foreach (var argg in callExpression.Arguments)
{
    rawArguments[i++] = Expression.Lambda(Expression.Convert(argg, argg.Type)).Compile().DynamicInvoke();
}  

و با این کد 

Expression.Lambda(Expression.Convert(argg, argg.Type)).Compile().DynamicInvoke();

هر آرگیومنت را به نوع خودش تبدیل کرده سپس Lambdaی آن را ساخته و کامپایل کرده تا Expression Tree به delegate  تبدیل شده ( به کد معادل IL تبدیل شده)، سپس آن را با متد DynamicInvoke  اجرا کرده تا دقیقا معادل آرگیومنت ارسالی را بدست آورده و در آرایه ذخیره می‌کنیم. در آخر آرایه بدست آمده را به بایت تبدیل کرده و می‌توان در یک منبع داده دائمی ذخیره کرد.

با کد زیر، Typeی که متد، درون آن قرار دارد را بدست می‌آوریم:

if (callExpression.Object != null)                            
    typeName = callExpression.Object.Type.ToString();         
else
    typeName = callExpression.Method.ReflectedType.ToString();

 و همچنین نام متد:

var methodname = callExpression.Method.Name;

تا به اینجای کار Type، نام متد و آرگیومنت‌های Serialize شده آن بدست آمدند که می‌توان آنها را در دیتابیس ذخیره کرد. سپس استخراج، DeSerialize و فراخوانی کرد:

نحوه فراخوانی:

var methodname = callExpression.Method.Name;                                   
var deserializedArgumentsObject = serializedArgumentsObject.ByteArrayToObject();                           
var objInstance = GetInstance(typeName);                                        
if (objInstance != null)
{
    objInstance.GetType().GetMethod(methodname).Invoke(objInstance, (object[])deserializedArgumentsObject);
}

و برای instance ساختن از Type مورد نظر از کد زیر استفاده می‌کنیم:

public object GetInstance(string strFullyQualifiedName)
        {
            Type type = Type.GetType(strFullyQualifiedName);
            if (type != null)
                return Activator.CreateInstance(type);

            foreach (var asm in AppDomain.CurrentDomain.GetAssemblies())
            {
                type = asm.GetType(strFullyQualifiedName);
                if (type != null)
                    return Activator.CreateInstance(type);
            }
            return null;
        }


موارد استفاده: نوشتن نرم افزارهایی برای مدیریت یک سری Job

کد کامل MethodCallSerialization.rar 

نظرات مطالب
EF Code First #4
connectionStrings   رو به این دو صورت میگذارم: 

 <add name="TestContext"
           connectionString="Data Source=localhost;Initial Catalog=Test;User ID=sa;Password=123;MultipleActiveResultSets=True;"
           providerName="System.Data.EntityClient" />
و
<add name="TestContext"
         connectionString="Data Source=localhost;Initial Catalog=Test;Integrated Security=True"
         providerName="System.Data.EntityClient" />
هر دو یه مشکل رو دارند.فکر نمی‌کنم چون اگر مشکل دسترسی اکانت بود منطقا بدون Migration هم باید خطا می‌داد؟!
نظرات مطالب
ASP.NET MVC #18
        [HttpGet]
        public ActionResult LogOn(string returnUrl)
        {
            if (User.Identity.IsAuthenticated) //remember me
            {
                if (ShouldRedirect(returnUrl))
                {
                    return Redirect(returnUrl);
                }
                return Redirect(FormsAuthentication.DefaultUrl);
            }

            return View(); // show the login page
        }
بعد  از پیاده سازی CustomRoleProvider وقتی که از این فیلتر استفاده می‌کنم :
[Authorize(Roles = "Admin")]
می ره به اکشن Logon اونجا
IsAuthenticated=true
هست و همین طور متد
ShouldRedirect(returnUrl)
هم مقدار true رو بر می‌گردونه و نتیجه اینکه دوباره بر می‌گرده به /User/Create و اونجا هم دوباره برمی گرده به همین اکشن و این Loop تکرار می‌شه.

این کد برای من اینجوری کار می‌کنه

مطالب
آشنایی با NHibernate - قسمت هشتم

معرفی الگوی Repository

روش متداول کار با فناوری‌های مختلف دسترسی به داده‌ها عموما بدین شکل است:
الف) یافتن رشته اتصالی رمزنگاری شده به دیتابیس از یک فایل کانفیگ (در یک برنامه اصولی البته!)
ب) باز کردن یک اتصال به دیتابیس
ج) ایجاد اشیاء Command برای انجام عملیات مورد نظر
د) اجرا و فراخوانی اشیاء مراحل قبل
ه) بستن اتصال به دیتابیس و آزاد سازی اشیاء

اگر در برنامه‌های یک تازه کار به هر محلی از برنامه او دقت کنید این 5 مرحله را می‌توانید مشاهده کنید. همه جا! قسمت ثبت، قسمت جستجو، قسمت نمایش و ...
مشکلات این روش:
1- حجم کارهای تکراری انجام شده بالا است. اگر قسمتی از فناوری دسترسی به داده‌ها را به اشتباه درک کرده باشد، پس از مطالعه بیشتر و مشخص شدن نحوه‌ی رفع مشکل، قسمت عمده‌ای از برنامه را باید اصلاح کند (زیرا کدهای تکراری همه جای آن پراکنده‌اند).
2- برنامه نویس هر بار باید این مراحل را به درستی انجام دهد. اگر در یک برنامه بزرگ تنها قسمت آخر در یکی از مراحل کاری فراموش شود دیر یا زود برنامه تحت فشار کاری بالا از کار خواهد افتاد (و متاسفانه این مساله بسیار شایع است).
3- برنامه منحصرا برای یک نوع دیتابیس خاص تهیه خواهد شد و تغییر این رویه جهت استفاده از دیتابیسی دیگر (مثلا کوچ برنامه از اکسس به اس کیوال سرور)، نیازمند بازنویسی کل برنامه می‌باشد.
و ...

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

این روزها تشکیل این لایه دسترسی به داده‌ها (data access layer یا DAL) نیز مزموم است! و دلایل آن در مباحث چرا به یک ORM نیازمندیم برشمرده شده است. جهت کار با ORM ها نیز نیازمند یک لایه دیگر می‌باشیم تا یک سری اعمال متداول با آن‌هارا کپسوله کرده و از حجم کارهای تکراری خود بکاهیم. برای این منظور قبل از اینکه دست به اختراع بزنیم، بهتر است به الگوهای طراحی برنامه نویسی شیء گرا رجوع کرد و از رهنمودهای آن استفاده نمود.

الگوی Repository یکی از الگوهای برنامه‌ نویسی با مقیاس سازمانی است. با کمک این الگو لایه‌ای بر روی لایه نگاشت اشیاء برنامه به دیتابیس تشکیل شده و عملا برنامه را مستقل از نوع ORM مورد استفاه می‌کند. به این صورت هم از تشکیل یک سری کدهای تکراری در سطح برنامه جلوگیری شده و هم از وابستگی بین مدل برنامه و لایه دسترسی به داده‌ها (که در اینجا همان NHibernate می‌باشد) جلوگیری می‌شود. الگوی Repository (مخزن)، کار ثبت،‌ حذف، جستجو و به روز رسانی داده‌ها را با ترجمه آن‌ها به روش‌های بومی مورد استفاده توسط ORM‌ مورد نظر، کپسوله می‌کند. به این شکل شما می‌توانید یک الگوی مخزن عمومی را برای کارهای خود تهیه کرده و به سادگی از یک ORM به ORM دیگر کوچ کنید؛ زیرا کدهای برنامه شما به هیچ ORM خاصی گره نخورده و این عملیات بومی کار با ORM توسط لایه‌ای که توسط الگوی مخزن تشکیل شده، صورت گرفته است.

طراحی کلاس مخزن باید شرایط زیر را برآورده سازد:
الف) باید یک طراحی عمومی داشته باشد و بتواند در پروژه‌های متعددی مورد استفاده مجدد قرار گیرد.
ب) باید با سیستمی از نوع اول طراحی و کد نویسی و بعد کار با دیتابیس، سازگاری داشته باشد.
ج) باید امکان انجام آزمایشات واحد را سهولت بخشد.
د) باید وابستگی کلاس‌های دومین برنامه را به زیر ساخت ORM مورد استفاده قطع کند (اگر سال بعد به این نتیجه رسیدید که ORM ایی به نام XYZ برای کار شما بهتر است، فقط پیاده سازی این کلاس باید تغییر کند و نه کل برنامه).
ه) باید استفاده از کوئری‌هایی از نوع strongly typed را ترویج کند (مثل کوئری‌هایی از نوع LINQ).


بررسی مدل برنامه

مدل این قسمت (برنامه NHSample4 از نوع کنسول با همان ارجاعات متداول ذکر شده در قسمت‌های قبل)، از نوع many-to-many می‌باشد. در اینجا یک واحد درسی توسط چندین دانشجو می‌تواند اخذ شود یا یک دانشجو می‌تواند چندین واحد درسی را اخذ نماید که برای نمونه کلاس دیاگرام و کلاس‌های متشکل آن به شکل زیر خواهند بود:



using System.Collections.Generic;

namespace NHSample4.Domain
{
public class Course
{
public virtual int Id { get; set; }
public virtual string Teacher { get; set; }
public virtual IList<Student> Students { get; set; }

public Course()
{
Students = new List<Student>();
}
}
}


using System.Collections.Generic;

namespace NHSample4.Domain
{
public class Student
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual IList<Course> Courses { get; set; }

public Student()
{
Courses = new List<Course>();
}
}
}

کلاس کانفیگ برنامه جهت ایجاد نگاشت‌ها و سپس ساخت دیتابیس متناظر

using FluentNHibernate.Automapping;
using FluentNHibernate.Cfg;
using FluentNHibernate.Cfg.Db;
using NHibernate.Tool.hbm2ddl;

namespace NHSessionManager
{
public class Config
{
public static FluentConfiguration GetConfig()
{
return
Fluently.Configure()
.Database(
MsSqlConfiguration
.MsSql2008
.ConnectionString(x => x.FromConnectionStringWithKey("DbConnectionString"))
)
.Mappings(
m => m.AutoMappings.Add(
new AutoPersistenceModel()
.Where(x => x.Namespace.EndsWith("Domain"))
.AddEntityAssembly(typeof(NHSample4.Domain.Course).Assembly))
.ExportTo(System.Environment.CurrentDirectory)
);
}

public static void CreateDb()
{
bool script = false;//آیا خروجی در کنسول هم نمایش داده شود
bool export = true;//آیا بر روی دیتابیس هم اجرا شود
bool dropTables = false;//آیا جداول موجود دراپ شوند
new SchemaExport(GetConfig().BuildConfiguration()).Execute(script, export, dropTables);
}
}
}
چند نکته در مورد این کلاس:
الف) با توجه به اینکه برنامه از نوع ویندوزی است، برای مدیریت صحیح کانکشن استرینگ، فایل App.Config را به برنامه افروده و محتویات آن‌را به شکل زیر تنظیم می‌کنیم (تا کلید DbConnectionString توسط متد GetConfig مورد استفاده قرارگیرد ):

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<connectionStrings>
<!--NHSessionManager-->
<add name="DbConnectionString"
connectionString="Data Source=(local);Initial Catalog=HelloNHibernate;Integrated Security = true"/>
</connectionStrings>
</configuration>

ب) در NHibernate سنتی (!) کار ساخت نگاشت‌ها توسط یک سری فایل xml صورت می‌گیرد که با معرفی فریم ورک Fluent NHibernate و استفاده از قابلیت‌های Auto Mapping آن، این‌کار با سهولت و دقت هر چه تمام‌تر قابل انجام است که توضیحات نحوه‌ی انجام ‌آن‌را در قسمت‌های قبل مطالعه فرمودید. اگر نیاز بود تا این فایل‌های XML نیز جهت بررسی شخصی ایجاد شوند، تنها کافی است از متد ExportTo آن همانگونه که در متد GetConfig استفاده شده، کمک گرفته شود. به این صورت پس از ایجاد خودکار نگاشت‌ها، فایل‌های XML متناظر نیز در مسیری که به عنوان آرگومان متد ExportTo مشخص گردیده است، تولید خواهند شد (دو فایل NHSample4.Domain.Course.hbm.xml و NHSample4.Domain.Student.hbm.xml را در پوشه‌ای که محل اجرای برنامه است خواهید یافت).

با فراخوانی متد CreateDb این کلاس، پس از ساخت خودکار نگاشت‌ها، database schema متناظر، در دیتابیسی که توسط کانکشن استرینگ برنامه مشخص شده، ایجاد خواهد شد که دیتابیس دیاگرام آن‌را در شکل ذیل مشاهده می‌نمائید (جداول دانشجویان و واحدها هر کدام به صورت موجودیتی مستقل ایجاد شده که ارجاعات آن‌ها در جدولی سوم نگهداری می‌شود).



پیاده سازی الگوی مخزن

اینترفیس عمومی الگوی مخزن به شکل زیر می‌تواند باشد:

using System;
using System.Linq;
using System.Linq.Expressions;

namespace NHSample4.NHRepository
{
//Repository Interface
public interface IRepository<T>
{
T Get(object key);

T Save(T entity);
T Update(T entity);
void Delete(T entity);

IQueryable<T> Find();
IQueryable<T> Find(Expression<Func<T, bool>> predicate);
}
}

سپس پیاده سازی آن با توجه به کلاس SingletonCore ایی که در قسمت قبل تهیه کردیم (جهت مدیریت صحیح سشن فکتوری)، به صورت زیر خواهد بود.
این کلاس کار آغاز و پایان تراکنش‌ها را نیز مدیریت کرده و جهت سهولت کار اینترفیس IDisposable را نیز پیاده سازی می‌کند :

using System;
using System.Linq;
using NHSessionManager;
using NHibernate;
using NHibernate.Linq;

namespace NHSample4.NHRepository
{
public class Repository<T> : IRepository<T>, IDisposable
{
private ISession _session;
private bool _disposed = false;

public Repository()
{
_session = SingletonCore.SessionFactory.OpenSession();
BeginTransaction();
}

~Repository()
{
Dispose(false);
}

public T Get(object key)
{
if (!isSessionSafe) return default(T);

return _session.Get<T>(key);
}

public T Save(T entity)
{
if (!isSessionSafe) return default(T);

_session.Save(entity);
return entity;
}

public T Update(T entity)
{
if (!isSessionSafe) return default(T);

_session.Update(entity);
return entity;
}

public void Delete(T entity)
{
if (!isSessionSafe) return;

_session.Delete(entity);
}

public IQueryable<T> Find()
{
if (!isSessionSafe) return null;

return _session.Linq<T>();
}

public IQueryable<T> Find(System.Linq.Expressions.Expression<Func<T, bool>> predicate)
{
if (!isSessionSafe) return null;

return Find().Where(predicate);
}

void Commit()
{
if (!isSessionSafe) return;

if (_session.Transaction != null &&
_session.Transaction.IsActive &&
!_session.Transaction.WasCommitted &&
!_session.Transaction.WasRolledBack)
{
_session.Transaction.Commit();
}
else
{
_session.Flush();
}
}

void Rollback()
{
if (!isSessionSafe) return;

if (_session.Transaction != null && _session.Transaction.IsActive)
{
_session.Transaction.Rollback();
}
}

private bool isSessionSafe
{
get
{
return _session != null && _session.IsOpen;
}
}

void BeginTransaction()
{
if (!isSessionSafe) return;

_session.BeginTransaction();
}


public void Dispose()
{
Dispose(true);
// tell the GC that the Finalize process no longer needs to be run for this object.
GC.SuppressFinalize(this);
}

protected virtual void Dispose(bool disposeManagedResources)
{
if (_disposed) return;
if (!disposeManagedResources) return;
if (!isSessionSafe) return;

try
{
Commit();
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
Rollback();
}
finally
{
if (isSessionSafe)
{
_session.Close();
_session.Dispose();
}
}

_disposed = true;
}
}
}
اکنون جهت استفاده از این کلاس مخزن به شکل زیر می‌توان عمل کرد:

using System;
using System.Collections.Generic;
using NHSample4.Domain;
using NHSample4.NHRepository;

namespace NHSample4
{
class Program
{
static void Main(string[] args)
{
//ایجاد دیتابیس در صورت نیاز
//NHSessionManager.Config.CreateDb();


//ابتدا یک دانشجو را اضافه می‌کنیم
Student student = null;
using (var studentRepo = new Repository<Student>())
{
student = studentRepo.Save(new Student() { Name = "Vahid" });
}

//سپس یک واحد را اضافه می‌کنیم
using (var courseRepo = new Repository<Course>())
{
var course = courseRepo.Save(new Course() { Teacher = "Shams" });
}

//اکنون یک واحد را به دانشجو انتساب می‌دهیم
using (var courseRepo = new Repository<Course>())
{
courseRepo.Save(new Course() { Students = new List<Student>() { student } });
}

//سپس شماره دروس استادی خاص را نمایش می‌دهیم
using (var courseRepo = new Repository<Course>())
{
var query = courseRepo.Find(t => t.Teacher == "Shams");

foreach (var course in query)
Console.WriteLine(course.Id);
}

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

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

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

ادامه دارد ...


مطالب
تست کردن متدهای یک Controller به کمک PowerShell

ابزارهای زیادی جهت تست کردن API ‌های برنامه‌های وب موجود است. یکی از معروفترین آنها  Fiddler است که ابزاری مستقل جهت دیباگ تحت پروتکل HTTP می‌باشد. یکی دیگر از این نرم افزارها Swashbuckle است که از Nuget قابل دریافت است و صفحه‌ای را به برنامه اضافه می‌کند که به اختصار API ‌های برنامه را نمایش داده و امکان اجرای آنها را فراهم میکند.

اما ساده‌ترین راه جهت کردن تست کردن API ‌های یک برنامه، استفاده از PowerShell است که عمل ایجاد درخواست‌های HTTP را به راحتی از طریق Command line فراهم میکند و به شما اجازه میدهد بدون وارد شدن به جزئیات، بر روی نتیجه API مورد نظر تمرکز کنید. PowerShell ابتدا فقط برای محیط ویندوز طراحی شده بود ولی در حال حاضر قابلیت اجرای تحت Linux و Mac را نیز دارد.

در این بخش به شما نشان خواهم داد که چگونه متدهای یک Controller را با استفاده از PowerShell تست نمایید.

بدین منظور ابتدا کلاسی را به نام Reservation با مشخصات زیر، در یک پروژه ASP.NET Core Web Application  ایجاد نمایید.
public class Reservation
{
        public int ReservationId { get; set; }
        public string ClientName { get; set; }
        public string Location { get; set; }
}
و سپس Interface زیر را جهت انجام عملیات CRUD اضافه نمائید:
public interface IRepository
{
        IEnumerable<Reservation> Reservations { get; }
        Reservation this[int id] { get; }
        Reservation AddReservation(Reservation reservation);
        Reservation UpdateReservation(Reservation reservation);
        void DeleteReservation(int id);
}
و کلاسی که Interface فوق را پیاده سازی خواهد کرد:
public class MemoryRepository : IRepository
 {
        private Dictionary<int, Reservation> items;
        public MemoryRepository()
        {
            items = new Dictionary<int, Reservation>();
            new List<Reservation> {
                new Reservation { ClientName = "Alice", Location = "Board Room" },
                new Reservation { ClientName = "Bob", Location = "Lecture Hall" },
                new Reservation { ClientName = "Joe", Location = "Meeting Room 1" }
            }.ForEach(r => AddReservation(r));
        }

        public Reservation this[int id] => items.ContainsKey(id) ? items[id] : null;
        public IEnumerable<Reservation> Reservations => items.Values;

        public Reservation AddReservation(Reservation reservation)
        {
            if (reservation.ReservationId == 0)
            {
                int key = items.Count;
                while (items.ContainsKey(key)) { key++; };
                reservation.ReservationId = key;
            }
            items[reservation.ReservationId] = reservation;
            return reservation;
        }

        public void DeleteReservation(int id) => items.Remove(id);
        public Reservation UpdateReservation(Reservation reservation) => AddReservation(reservation);
}
در کلاس فوق به جهت سهولت در کار، از یک لیست، برای نگهداری تعدادی رکورد اطلاعاتی جهت نمایش استفاده شده است.

سپس کنترلری را به نام ReservationController به پروژه اضافه کنید که شامل متدهای زیر باشد:
[Route("api/[controller]")]
public class ReservationController : Controller
    {
        private IRepository repository;
        public ReservationController(IRepository repo) => repository = repo;

        [HttpGet]
        public IEnumerable<Reservation> Get() => repository.Reservations;

        [HttpGet("{id}")]
        public Reservation Get(int id) => repository[id];

        [HttpPost]
        public Reservation Post([FromBody] Reservation res) =>
                    repository.AddReservation(new Reservation
                    {
                        ClientName = res.ClientName,
                        Location = res.Location
                    });
        [HttpPut]
        public Reservation Put([FromBody] Reservation res) => repository.UpdateReservation(res);

        [HttpPatch("{id}")]
        public StatusCodeResult Patch(int id, [FromBody] JsonPatchDocument<Reservation> patch)
        {
            Reservation res = Get(id);
            if (res != null)
            {
                patch.ApplyTo(res);
                return Ok();
            }
            return NotFound();
        }

        [HttpDelete("{id}")]
        public void Delete(int id) => repository.DeleteReservation(id);
}
حالا جهت تست صحت این API‌ها توسط PowerShell لازم است ابتدا برنامه را اجرا کرده و منتظر بمانید تا صفحه Home ظاهر شود. پس از آن لازم است یک پنجره PowerShell را از طریق کلیک راست در یکی از فولدرهای نمایش داده شده در FileExplorer باز کنید.

تست کردن درخواست از نوع GET

حالا دستور زیر را در پنجره PowerShell وارد کنید:
 Invoke-RestMethod http://localhost:7000/api/reservation -Method GET
لازم است شماره پورت را مطابق با پورتی که برنامه شما بر روی آن اجرا شده تغییر دهید. این فرمان به کمک دستور Invoke-RestMethod درخواستی از نوع GET را به api/reservation ارسال می‌کند و نتیجه را پس از تجزیه و تحلیل و فرمت بندی، جهت سهولت در خوانده شدن، به شکل زیر نمایش میدهد:

location  clientName  reservationId 
Board Room 
Alice 
0                  
Lecture Hall 
Bob
1
Meeting Room 1 
Joe
2
فرمت اطلاعات دریافت شده از درخواست GET، اشیایی از نوع کلاس Reservation است و به فرمت Json می‌باشد؛ ولی Invoke-RestMethod آنرا به صورت جدول نمایش میدهد.
حال جهت نمایش فقط یک رکورد از اطلاعات فوق، دستور زیر را وارد نمایید:
Invoke-RestMethod http://localhost:7000/api/reservation/1 -Method GET
این فرمان درخواستی جهت دریافت اطلاعات شیء Reservation است که کد داخلی آن برابر 1 است. پاسخ این درخواست به شکل زیر نمایش داده خواهد شد:

location  clientName   reservationId   
Lecture Hall  
        Bob
1                  


تست درخواست از نوع Post

تمامی متدهای ارائه شده توسط یک API را می‌توان به کمک PowerShell تست کرد. اگرچه در این حالت فرمت بعضی از درخواستهای ارسالی ممکن است کمی به هم ریخته به نظر برسد. در اینجا درخواستی جهت اضافه کردن یک رکورد را به لیست Reservation ارسال می‌کنیم و نتیجه را در پاسخ ارسال شده از سمت سرور خواهیم دید:

Invoke-RestMethod http://localhost:7000/api/reservation -Method POST -Body
(@{clientName="Anne"; location="Meeting Room 4"} | ConvertTo-Json) -ContentType "application/json"
این دستور با استفاده از آرگومان Body- محتویات درخواست را که با فرمت Json است تعیین می‌کند. آرگومان Content-Type- هم نوع محتویات درخواستی را در هدر مشخص میکند. دستور فوق در صورت موفقیت نتیجه زیر را نمایش خواهد داد:

location  clientName   reservationId   
 Meeting Room 4
Anne      
1                
           
دستور Post ارسالی، با استفاده از دو فیلد clientName و location، یک رکورد جدید از نوع Reservation را به لیست موجود اضافه کرده و نتیجه را در قالب Json به سمت کلاینت برگشت خواهد داد که شامل ReservationId می‌باشد که به رکورد جدید اختصاص داده شده‌است. ممکن است چنین به نظر برسد که نتیجه برگشت داده شده، همان درخواست ارسالی است که به شکل جدول فوق نمایش داده می‌شود؛ ولی چنین نیست. جهت مشاهده تاثیر درخواست POST ارسالی بر روی منبع داده، کافی است یک بار دیگر دستور زیر را در command line وارد کنید:
Invoke-RestMethod http://localhost:7000/api/reservation -Method GET

location  clientName   reservationId   
 Board Room           Alice
0              
 Lecture Hall  Bob 1
 Meeting Room 1  Joe 2
 Meeting Room 4  Anne 3
داده‌هایی که به سمت کلاینت ارسال شده، نشان دهنده اضافه شدن رکورد جدیدی به منبع داده ماست.

تست درخواست از نوع PUT

درخواست از نوع PUT جهت جایگزینی داده‌ای موجود در لیست، با داده‌ی جدید است. بدین منظور کد داخلی شیء مورد نظر (در اینجا ReservationId) باید در URL گنجانده شود و مقادیر فیلدهای کلاس، در متن درخواست. مثالی جهت درخواست اصلاح داده موجود از طریق فرمان PowerShell :

Invoke-RestMethod http://localhost:7000/api/reservation -Method PUT -Body
(@{reservationId="1"; clientName="Bob"; location="Media Room"} | ConvertTo-Json)
-ContentType "application/json"
درخواست فوق، فیلد Location رکوردی با کد داخلی 1 را به مقدار جدیدی تغییر خواهد داد و نتیجه تغییر انجام شده را در پاسخ ارسال خواهد کرد:

location  clientName   reservationId   
Media Room
Bob        
1                

و باز با ارسال درخواست زیر می‌توان نتیجه کلی را مشاهده کرد:
Invoke-RestMethod http://localhost:7000/api/reservation -Method GET

location  clientName   reservationId   
Board Room
Alice  
0  
 Media Room
 Bob  1
 Meeting Room 1
Joe 2
 Meeting Room 4
Anne
3


استفاده از دستور PATCH

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

ASP.NET Core MVC از استاندارد Json PATCH پشتیبانی می‌کند و این کار اجازه میدهد تا بتوان تغییرات را بطور یکنواختی انجام داد. جهت مشاهده جزئیات این دستور می‌توانید به این لینک مراجعه کنید. اما برای مثال فرض کنید می‌خواهید چنین درخواستی را که به فرمت Json است از طریق HTTP PATCH به سمت سرور ارسال کنید:

[
  { "op": "replace", "path": "clientName", "value": "Bob"},
  { "op": "replace", "path": "location", "value": "Lecture Hall"}
]

دربسیاری از مواقع فقط از دستور replace درخواست PATCH استفاده می‌شود و کد داخلی رکورد مورد نظر، جهت تغییر در Url درخواست ارسالی، فرستاده خواهد شد. ASP.NET Core MVC به طور اتوماتیک این دستور را پردازش کرده و آنرا به صورت آبجکتی از نوع <JsonPatchDocument<T به اکشن کنترلر قید شده، پاس خواهد داد که در اینجا نوع T، از نوع کلاس مورد نظر ما می‌باشد. فرمت دستور ارسالی از طریق Powershell به شکل زیر خواهد بود:

Invoke-RestMethod http://localhost:7000/api/reservation/2 -Method PATCH -Body (@
{ op="replace"; path="clientName"; value="Bob"},@{ op="replace"; path="location";
value="Lecture Hall"} | ConvertTo-Json) -ContentType "application/json"
دستور فوق درخواست تغییر دو فیلد clientname و location، با کد داخلی 2 می‌باشد.
جهت مشاهده تغییر ایجاد شده، دستور زیر را مجددا اجرا نمایید:
Invoke-RestMethod http://localhost:7000/api/reservation -Method GET

location  clientName   reservationId  
Board Room
Alice  
0            
Media Room
Bob
 1
Lecture Hall
 Bob 2
Meeting Room 4
 Anne 3


استفاده از دستور Delete

استفاده از دستور Delete هم به شکل زیر خواهد بود:

Invoke-RestMethod http://localhost:7000/api/reservation/2 -Method DELETE
توضیح اینکه در این حالت پاسخی از سمت سرور ارسال نخواهد شد. اما جهت مشاهده تاثیر این دستور بر روی اطلاعات موجود می‌توان مجددا دستور زیر را جهت نمایش داده‌ها بکار برد:
Invoke-RestMethod http://localhost:7000/api/reservation -Method GET

کدهای این مقاله به پیوست موجود است: ApiControllers.zip
بازخوردهای دوره
متدهای async تقلبی
برای استفاده از یک وب سرویس، اگر به شکل زیر در یک ClassLibrary استفاده شود، آیا واقعا غیر همزمان عمل می‌شود یا تقلبی محسوب می‌شود؟
public bool SendSms()
{
    try
    {
        var result = false;
        foreach (var nomber in _fromNombers)
        {
            if (result) continue;
            if (_smsService.SendGroupSmsSimple(Model.Signature, nomber, Model.Numbers, Model.Body, false,
                string.Empty) == 1)
                result = true;
        }
        return result;
    }
    catch (Exception exception)
    {
        Console.WriteLine(exception.Message);
        return false;
    }
}

private Task SendSmsAsync()
{
    return Task.Run(() => SendSms());
}
و مثلا در یک ConsoleApp:
static void Main(string[] args)
{
    var r = SendAsync();
    r.Wait();
}

private static async Task SendAsync()
{
    var model = new SmsModel
    {
        Body = "سلام",
        Numbers = new[] { "0917*******" }
    };

    var service = new SmsService(model);

    await service.SendSmsAsync();
}

مطالب
MongoDB #6
ساخت مجموعه در MongoDB

متد ()CreateCollection
دستور (db.createCollection(name, options در MongoDB برای ساخت مجموعه بکار برده می‌شود.

گرامر:
گرامر پایه دستور ()createCollection به شکل زیر است:
(db.createCollection(name, options
در این دستور، پارامتر name نام مجموعه‌ای است که باید ساخته شود. پارامتر Option یک سند است و برای تعیین پیکربندی مجموعه استفاده می‌شود.
پارامتر نوع توضیحات 
 name  رشته  نام مجموعه‌ای است که باید ساخته شود
 Option  سند  (اختیاری) تعیین  اختیارات برای اندازه حافظه و ایندکس گذاری
 پارامتر Option اختیاری است. در جدول زیر لیست اختیارتی را که می‌توانید استفاده کنید آمده است:
 فیلد  نوع توضیحات 
capped
Boolean
 (اختیاری) اگر مقدار آن true باشد یک مجموعه‌ی پوشیده (capped) در اختیار می‌گذارد. مجموعه‌ی پوشیده یک مجموعه با اندازه ثابت است که وقتی به حداکثر اندازه خود برسد، داده‌های جدید را بصورت اتوماتیک جایگزین قدیمی‌ترین داده‌ها می‌کند. اگر این پارامتر را true تنظیم کرده باشید، باید پارامتر size راهم مقداردهی کنید.
AutoIndexID Boolean
 (اختیاری) اگر true باشد، بصورت اتوماتیک روی فیلد _id ایندکس می‌سازد.  مقدار پیش فرض این پارامتر false است.
 size number (اختیاری) تعیین کننده‌ی حداکثر اندازه به بایت برای مجموعه پوشیده. اگر پارامتر capped برابر true باشد آنگاه نیاز دارید این پارامتر را نیز مقداردهی کنید.
 max number  (اختیاری) تعیین کننده حداکثر تعداد سندهای مجاز در یک مجموعه پوشیده

هنگام درج یک سند، MongoDB ابتدا فیلد capped و سپس فیلد max را بررسی می‌کند.


مثال:

گرامر پایه متد ()createCollection بدون اختیارات به شکل زیر است:

>use test
switched to db test
>db.createCollection("mycollection")
{ "ok" : 1 }
>

با استفاده از دستور show collection می‌توانید مجموعه ساخته شده را بررسی کنید:

>show collections
mycollection
system.indexes

مثال زیر گرامر متد ()createCollection، با اختیارات مهم‌تر را نمایش می‌دهد:

>db.createCollection("mycol", { capped : true, autoIndexID : true, size : 6142800, max : 10000 } )
{ "ok" : 1 }
>

در MongoDB، نیازی به ساخت مجموعه ندارید. وقتی یک سند را درج کنید، MongoDB بصورت اتوماتیک مجموعه را می‌سازد.

>db.tutorialspoint.insert({"name" : "tutorialspoint"}) 
>show collections 
mycol 
mycollection 
system.indexes 
tutorialspoint 
>

حذف مجموعه‌ها در MongoDB

متد ()drop

دستور ()db.collection.drop  برای حذف یک مجموعه از پایگاه داده استفاده می‌شود.

گرامر:

گرامر پایه دستور ()drop به شکل زیر است:

 db.COLLECTION_NAME.drop()

مثال:

ابتدا همه مجموعه‌های موجود در پایگاه داده mydb را بررسی کنید:

 >use mydb
switched to db mydb
>show collections
mycol
mycollection
system.indexes
tutorialspoint
>
اکنون مجموعه با نام mycollection را حذف کنید:
 >db.mycollection.drop()
true
>
دوباره لیست مجموعه‌های داخل پایگاه داده را بررسی کنید:
 >show collections
mycol
system.indexes
tutorialspoint
>
اگر مجموعه انتخاب شده اه موفقیت حذف شود، متد ()drop مقدار true درغیر این صورت مقدار false را برمی‌گرداند.
مطالب
یافتن خطاهای متداول کدهای جاوا اسکریپتی با غنی سازی تنظیمات کامپایلر TypeScript
یکی از اهداف مهم استفاده‌ی از TypeScript، یافتن خطاهای متداول کدهای جاواسکریپتی، پیش از اجرای آن‌ها در مرورگر است. برای مثال، قطعه کد زیر:
  defaultChecks() {
    const author = { firstName: "Vahid", lastName: "N" };
    console.log(author.lastname);
    author.lastName.trimStart();
    author.firstName.charCodeAt("1");
  }
دارای سه مشکل است که سریعا توسط TypeScript شناسایی می‌شود:


- خاصیت lastname در شیء author وجود خارجی ندارد.
- نوع رشته‌ای، به همراه متد trimStart نیست.
- متد charCodeAt یک عدد را به عنوان پارامتر قبول می‌کند.

اما باید درنظر داشت که بسیاری از قابلیت‌های بررسی کد TypeScript، به صورت پیش‌فرض فعال نیستند که در ادامه آن‌ها را برای یافتن پیش از موعود بسیاری از مشکلات، فعالسازی خواهیم کرد.


نصب افزونه‌ی TSLint در VSCode

جهت مشاهده‌ی بهتر خطاهای کامپایلر TypeScript، پیش از کامپایل نهایی کدها، می‌توان از افزونه‌ی TSLint استفاده کرد. برای نصب آن، ابتدا باید بسته‌ی ذیل را نصب کرد:
  > npm install -g tslint typescript
سپس نیاز است افزونه‌ی آن‌را نیز نصب کنید: https://marketplace.visualstudio.com/items?itemName=eg2.tslint
کار TSLint انجام static code analysis است؛ چیزی شبیه به افزونه‌هایی مانند ری‌شارپر در ویژوال استودیو که راهنماهایی را در مورد بهتر کردن کیفیت کدهای نوشته شده ارائه می‌دهد.


فعالسازی بررسی نال و نوع‌های نال پذیر

 strictNullChecks یکی از مهم‌ترین پرچم‌های تنظیمات کامپایلر تایپ‌اسکریپت است. برای افزودن آن، به فایل tsconfig.json مراجعه کرده و پرچم آن‌را به true تنظیم کنید:
{
    "compilerOptions": {
        "strictNullChecks": true
    }
}
به این ترتیب کامپایلر تایپ‌اسکریپت، بین null ،undefined و سایر نوع‌ها، تفاوت قائل خواهد شد و اگر نوعی نال‌پذیر است، باید آن‌را به صورت صریح type | null تعریف کنید.
برای مثال، متد ذیل را در نظر بگیرید:
  getSessionItem(key: string): any {
    const data = window.sessionStorage.getItem(key);
    return JSON.parse(data);
  }
زمانیکه پرچم strictNullChecks فعال شود، قطعه کد فوق با خطای ذیل کامپایل نخواهد شد:
[ts]
Argument of type 'string | null' is not assignable to parameter of type 'string'.
Type 'null' is not assignable to type 'string'.
const data: string | null
از این جهت که خروجی متد getItem به صورت string | null تعریف شده‌است؛ اما پارامتر متد JSON.parse فقط string را قبول می‌کند. یعنی ممکن است data دریافتی نال باشد که متد JSON.parse قادر به پردازش آن نیست.
برای رفع این مشکل تنها کافی است بررسی کنیم که آیا data نال است یا خیر؟ و اگر خیر، آنگاه آن‌را به متد JSON.parse ارسال کنیم:
  getSessionItem(key: string): any {
    const data = window.sessionStorage.getItem(key);
    if (data) {
      return JSON.parse(data);
    } else {
      return null;
    }
  }
همانطور که ملاحظه می‌کنید، فعالسازی strictNullChecks می‌تواند از بروز بسیاری از خطاهای در زمان اجرای برنامه، جلوگیری کند.


گزارش return‌های فراموش شده

در متد ذیل، یک return فراموش شده وجود دارد و تمام شرط‌های برنامه به یک خروجی مشخص، منتهی نمی‌شوند:
  noImplicitReturns(a: number) {
    if (a > 10) {
      return a;
    }
    // No return in this branch
  }
برای تشخیص زود هنگام یک چنین مشکلاتی می‌توان پرچم noImplicitReturns را در فایل tsconfig.json به true تنظیم کرد:
{
    "compilerOptions": {
        "noImplicitReturns": true
    }
}
پس از این تنظیم، کامپایلر در مورد متد noImplicitReturns، خطای ذیل را صادر می‌کند:
 [ts] Not all code paths return a value.


تشخیص کدهای مرده

قطعه کدی که پس از یک return قرار بگیرد، یک کد مرده نامیده می‌شود. با تنظیم پرچم allowUnreachableCode در فایل tsconfig.json به false، می‌توان کامپایلر TypeScript را وادار کرد تا اینگونه موارد را به عنوان خطا گزارش کند:
{
    "compilerOptions": {
        "allowUnreachableCode": false
    }
}
پس از این فعالسازی، کامپایلر TypeScript دو خطای [ts] Unreachable code detected را در مورد قطعه کد ذیل صادر می‌کند:
    allowUnreachableCode() {
      if (false) {
        console.log("Unreachable code");
      }
      const a = 1;
      if (a > 0) {
        return 10; // reachable code
      }
      return 0;
      console.log("Unreachable code");
    }
مورد اول مربوط به بدنه‌ی if ایی است که شرط آن false است و این بدنه هیچگاه فراخوانی نمی‌شود و مورد دوم، به سطر پس از return آخر مرتبط است که آن مورد نیز یک کد مرده محسوب می‌شود.


تشخیص پارامترها و متغیرهای استفاده نشده

دو متد ذیل را درنظر بگیرید:
  unusedLocals() {
    const a = "foo"; // Error: 'a' is declared but its value is never read
    return "bar";
  }

  unusedParameters(n: number) {
    n = 0; // Never read
  }
در اولی متغیر a تعریف شده‌است، اما هیچگاه استفاده نشده‌است. در متد دوم، پارامتر n نیز هیچگاه خوانده نشده‌است و وجود آن بی‌مصرف است؛ حتی ممکن است نشان فراموش شدن استفاده‌ی از آن و مقدار دهی اشتباه آن باشد.
برای فعالسازی بررسی یک چنین مواردی باید دو پرچم ذیل را در فایل tsconfig.json به true تنظیم کرد:
{
    "compilerOptions": {
        "noUnusedLocals": true,
        "noUnusedParameters": true
    }
}
پس از آن، کامپایلر دو خطای ذیل را در مورد متدهای فوق، گزارش می‌کند:
[ts] 'a' is declared but its value is never read.
[ts] 'n' is declared but its value is never read.


یافتن خواصی که نباید در یک شیء وجود داشته باشند

در مثال ذیل، خاصیت baz در تعاریف اصلی نوع‌های x و y وجود ندارد:
  excessPropertyForObjectLiterals() {
    let x: { foo: number };
    x = { foo: 1, baz: 2 };  // Error, excess property 'baz'
    let y: { foo: number, bar?: number };
    y = { foo: 1, baz: 2 };  // Error, excess property 'baz'
  }
برای فعالسازی یافتن یک چنین مواردی که در بسیاری از حالات ممکن است ناشی از اشتباهات تایپی نیز باشند، می‌توان پرچم suppressExcessPropertyErrors را در فایل tsconfig.json به false تنظیم کرد:
{
    "compilerOptions": {
        "suppressExcessPropertyErrors": false
    }
}
پس از آن برای نمونه در مورد انتساب اول، یک چنین پیام خطایی از طرف کامپایلر TypeScript صادر خواهد شد:
[ts]
Type '{ foo: number; baz: number; }' is not assignable to type '{ foo: number; }'.
Object literal may only specify known properties, and 'baz' does not exist in type '{ foo: number; }'.
(property) baz: number


یافتن breakهای فراموش شده در عبارات switch

در مثال زیر، یک break فراموش شده‌است:
  fallthroughCasesInSwitchStatement(a: number) {
    switch (a) {
      case 0:
        break;

      case 1:
        a += 1;

      case 2:
        a += 2;
        break;
    }
  }
برای تشخیص آن توسط کامپایلر TypeScript باید پرچم noFallthroughCasesInSwitch را در فایل tsconfig.json به true تنظیم کرد:
{
    "compilerOptions": {
        "noFallthroughCasesInSwitch": true
    }
}
پس از آن کامپایلر TypeScript خطای «[ts] Fallthrough case in switch» را در مورد متد فوق صادر می‌کند.


یافتن ایندکس‌های تعریف نشده‌ی در اشیاء

در مثال زیر، شیء x دارای خاصیت b نیست؛ اما دقیقا با این ایندکس مورد استفاده قرار گرفته‌است:
  indexingObjectsLackingIndexSignatures() {
    const x = { a: 0 };
    x["a"] = 1; // ok
    x["b"] = 1; // Error, type '{ a: number; }' has no index signature.
  }
برای تشخیص یک چنین خطاهایی می‌توان پرچم suppressImplicitAnyIndexErrors را در فایل tsconfig.json به false تنظیم کرد:
{
    "compilerOptions": {
        "suppressImplicitAnyIndexErrors": false
    }
}
پس از آن کامپایلر TypeScript خطای ذیل را در مورد دسترسی به ایندکسی که با امضای شیء سازگاری ندارد، صادر می‌کند:
[ts] Element implicitly has an 'any' type because type '{ a: number; }' has no index signature.


اجبار به تعریف صریح نوع‌ها در TypeScript

عمده‌ی قابلیت TypeScript در یافتن خطاها به تعاریف نوع‌ها و راهنمایی کامپایلر آن در این زمینه بر می‌گردد. اما چون این زبان سازگاری کاملی را با JavaScript دارد، تعریف نوع‌ها در آن اجباری نیست و در این حالت اگر نوعی تعریف نشده باشد، به any تفسیر می‌شود. جهت اجبار به تعریف نوع‌ها در TypeScript می‌توان پرچم noImplicitAny را در فایل tsconfig.json به true تنظیم کرد:
{
    "compilerOptions": {
        "noImplicitAny": true
    }
}
در این حالت دیگر قطعه کد ذیل کامپایل نخواهد شد:
  noImplicitAny(args) { // Error: Parameter 'args' implicitly has an 'any' type.
    console.log(args);
  }
برای رفع این مشکل می‌توان نوع args را به صورت صریحی مشخص کرد:
  noImplicitAnyArgs(args: string[]) { // ok with the type information
    console.log(args);
  }


یک نکته‌ی تکمیلی
اگر از دستور ng build --watch برای ساخت برنامه‌های Angular استفاده می‌کنید، تغییرات فوق زمانی تاثیر داده خواهند شد که یکبار این برنامه را بسته و مجددا اجرا کنید.
مطالب
آشنایی با WPF قسمت چهارم: کنترل ها
WPF همانند Windows Form شامل ابزارها یا کنترل‌های داخلی است که می‌توانند در تهیه‌ی یک برنامه بسیار کارآمد باشند. در این بخش به بررسی تعدادی از این کنترل‌ها می‌پردازیم و مابقی آن‌ها را در قسمت‌های آینده بررسی خواهیم کرد. در این نوشتار سعی بر این است که یک فرم ساده را با آن ایجاد کرده و مورد استفاده قرار دهیم.
این فرم دارای اطلاعاتی شامل : نام، جنسیت ، زمینه‌های کاری، کشور، تاریخ تولد و تصویر می‌باشد.

TextBlock

همان Label قدیمی خودمان است که برای نمایش متون کاربر دارد. متن داخل آن بین دو تگ قرار می‌گیرد و یا از خاصیت Text آن کمک گرفته خواهد شد. حتما از خاصیت Width و height آن برای مقداردهی کمک بگیرید، زیرا در غیر آن صورت کل Container خود را خواهد پوشاند. در صورتی که متنی در مکان خود جا نشود می‌توان از دو ویژگی استفاده کرد. آن را برش داد یا به خطوط بعدی شکست. برای حذف یا برش باقی مانده متن می‌توان از خصوصیت TextTrimming استفاده کرد که سه مقدار می‌گیرد:

None مقدار پیش فرض

CharacterEllipsis با نزدیک شدن به آخر پهنای کار از ... استفاده می‌نماید. در صورتی که لیستی یا مورد مشابهی دارید میتواند بسیار کاربردی باشد.

WordEllipsis این گزینه هم مانند مورد بالاست با این تفاوت که سعی دارد تا آنجا که ممکن است خود را به آخرین حرف کلمه برساند تا شکستگی در وسط کلمه اتفاق نیفتد و آخرین کلمه کامل دیده شود و بعد ... قرار بگیرد؛ هر چند در تست‌های خودم تفاوتی مشاهده نکردم.

گزینه TextWrapping جهت شکستن یک خط به خطوط است؛ موقعی که متن شما به انتهای صفحه می‌رسد، این ویژگی باعث می‌شود متن به بیرون از پنجره نرفته و یک خط به سمت پایین حرکت کند. این گزینه سه مقدار را دارد:

تصویر زیر حالت اصلی نمایش بدون نیاز به Wrap شدن است:

None: مقدار پیش فرض که خصوصیت Wrap را به همراه ندارد.

Wrap: فعال سازی ویژگی TextWrapping

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

خصوصیت LineStackingStrategy:

این خصوصیت فاصله‌ی بین خطوط را با استفاده از یک واحد منطقی dp مشخص می‌کند. هر چند دو گزینه دیگر هم دارد که دو تصویر زیر را در این صفحه به شما نمایش می‌دهد:

برای ساخت فرم از یک گرید با سه ستون و 6 سطر استفاده می‌کنم.

<Grid Margin="5">
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="Auto"></ColumnDefinition>
        <ColumnDefinition Width="*"></ColumnDefinition>
            <ColumnDefinition Width="Auto"></ColumnDefinition>
    </Grid.ColumnDefinitions>
    <Grid.RowDefinitions>
            <RowDefinition Height="Auto"></RowDefinition>
            <RowDefinition Height="Auto"></RowDefinition>
            <RowDefinition Height="Auto"></RowDefinition>
            <RowDefinition Height="Auto"></RowDefinition>
            <RowDefinition Height="Auto"></RowDefinition>
            <RowDefinition Height="Auto"></RowDefinition>
        
        </Grid.RowDefinitions>
</Grid>
در ستون اول نام فیلدهای مورد نظر را می‌نویسیم و در ستون دوم هم کنترل‌های مد نظر هر فیلد را قرار خواهیم داد. در صورتی که دوست دارید کار از راست به چپ پشتیبانی کند از گزینه OverflowDirection در تگ پنجره Window استفاده نمایید.
در داخل گرید بعد از تعریف سطر و ستون، همانطور که قبلا توضیح دادیم کنترل‌های TextBlock را اضافه می‌کنیم:
<TextBlock Grid.Column="0"  Grid.Row="0" VerticalAlignment="Center" HorizontalAlignment="Left" >Name</TextBlock>
        <TextBlock Grid.Column="0"  Grid.Row="1" VerticalAlignment="Center" HorizontalAlignment="Left" >Gender</TextBlock>
        <TextBlock Grid.Column="0"  Grid.Row="2" VerticalAlignment="Center" HorizontalAlignment="Left" >Field Of Work</TextBlock>
        <TextBlock Grid.Column="0"  Grid.Row="3" VerticalAlignment="Center" HorizontalAlignment="Left" >Country</TextBlock>
        <TextBlock Grid.Column="0"  Grid.Row="4" VerticalAlignment="Center" HorizontalAlignment="Left" >Birth Date</TextBlock>

<TextBox Grid.Row="0" Grid.Column="1" Name="Txtname" HorizontalAlignment="Left" Margin="5" Width="200" ></TextBox>
برای فیلد نام، از کنترل TextBox استفاده کردم که با محدود کردن Width آن اندازه ثابت به آن دادم. در صورتی که width ذکر نشود یا به Auto ذکر شود، در صورتی که متنی که کاربر تایپ می‌کند، بیش از اندازه تعیین شده کنترل Textbox باشد، کنترل هم همراه متن بزرگتر خواهد شد و تا پایان محدوده سلولی اش در گرید کش خواهد آمد.

Buttons 
برای فیلد جنسیت Gender هم از RadioButton کمک گرفتم که با استفاده از خاصیت GroupName می‌توان دسته‌ای از این کنترل‌ها را با هم مرتبط ساخت تا با انتخاب یک آیتم جدید از همان گروه، آیتم قبلی که انتخاب شده بود از حالت انتخاب خارج شده و آیتم جدیدی انتخاب شود. از خاصیت IsChecked می‌توان برای انتخاب یک آیتم بهره برد.

به صورت کلی دکمه‌ها به چند دسته زیر تقسیم می‌شوند:

  • Button
  • ToggleButton
  • CheckBox
  • RadioButton

که همگی این عناصر از کلاسی به نام ButtonBase مشتق شده اند. کد زیر RadioButton‌ها را به صورت عمودی چینش کرده است:

<StackPanel Orientation="Vertical" Grid.Row="1" Grid.Column="1" Margin="10">
            <RadioButton GroupName="Gender" Name="RdoMale" IsChecked="True" >Male</RadioButton>
            <RadioButton GroupName="Gender" Name="RdoFemale" Margin="0 5 0 0" >Female</RadioButton>
        </StackPanel>
برای فیلد زمینه کاری ، لیست کشورها و تاریخ تولد از کدهای زیر کمک گرفتم:
<StackPanel Orientation="Horizontal" Grid.Row="2" Grid.Column="1" Margin="10">
            <CheckBox Name="ChkActor" >Actor/Actress</CheckBox>
            <CheckBox Name="ChkDirector" >Director</CheckBox>
            <CheckBox Name="ChkProducer" >Producer</CheckBox>
        </StackPanel>

        <ListBox Grid.Row="3" Grid.Column="1" Margin="10"  Height="80">
        <ListBoxItem>
                <TextBlock>UnitedStates</TextBlock>
            </ListBoxItem>
            <ListBoxItem>
                <TextBlock >UK</TextBlock>
            </ListBoxItem>
            <ListBoxItem>
                <TextBlock >France</TextBlock>
            </ListBoxItem>
            <ListBoxItem>
                <TextBlock >Japan</TextBlock>
            </ListBoxItem>
        </ListBox>

        <Calendar Grid.Row="4" Grid.Column="1" HorizontalAlignment="Left" Margin="10"></Calendar>
برای لیست کشورها می‌توان از یک ListBox یا ComboBox استفاده کرده که هر آیتم داخل آنها در یک تگ ListBoxItem یا ComboBoxItem قرار می‌گیرد. اگر از حالت ListBox استفاده می‌کنید، در صورتی که آیتم‌ها از ارتفاع لیست بیشتر شود به طور خودکار یک Scrollbar برای آن‌ها در نظر گرفته خواهد شد و نیازی نیست که آن را دستی اضافه کنید.
برای تصویر شخص، قصد دارم آن را در گوشه‌ی سمت راست و بالا قرار دهم. برای همین محل ستون آن را ستون سوم یا اندیس دوم انتخاب کرده و از آنجا که این عکس حالت پرسنلی دارد، می‌تواند چند سطر را به خود اختصاص دهد که با کمک خاصیت Rowspan، چهار سطر، کنترل را ادامه دادم. برای ستون‌ها هم می‌توان از خاصیت ColumnSpan استفاده کرد. همچنین دوست دارم یک دکمه هم روی تصویر در گوشه‌ی سمت چپ و پایین قرار داده که کاربر با انتخاب آن به انتخاب عکس یا تغییر آن بپردازد. برای همین از یک پنل گرید استفاده کردم و کنترل دکمه را روی تصویر قرار دادم. همپوشانی کنترل‌ها در اینجا صورت گرفته است.

 <Grid Grid.Row="0" Grid.Column="2" Grid.RowSpan="4">
            <Grid.RowDefinitions>
                <RowDefinition Height="*"></RowDefinition>
            </Grid.RowDefinitions>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="*"></ColumnDefinition>
            </Grid.ColumnDefinitions>
            <Image HorizontalAlignment="Right" Source="man.jpg" Stretch="UniformToFill" VerticalAlignment="Top" Width="100" Height="150"></Image>
            <Button Width="25" Height="15"    Padding="0"  HorizontalAlignment="Left" VerticalAlignment="Bottom" Margin="0,0,0,83">
                 <TextBlock VerticalAlignment="Center" Margin="0 -7 0 0">...</TextBlock>
            </Button>
        </Grid>
خاصیت Stretch کنترل Image در بالا، نحوه‌ی نمایش تصویر را نشان می‌دهد که چهار مقدار دارد:
None: تصویر، اندازه‌ی اصلی خود را حفظ کرده و هر مقدار آن که در کنترل جا شود، نمایش می‌یابد و بسته به سایز تصویر ممکن است گوشه هایی از تصویر نمایش نیابد.
Fill: تصویر را داخل کنترل به زور جا داده تا پهنا و ارتفاع عکس، هم اندازه کنترل می‌شود.
Uniform: تصویر بزرگ را با در نظر گرفتن نسبت پهنا و ارتفاع تصویر، با یکدیگر در کنترل جا می‌دهد.
UniformToFill: تصویر، کل کنترل را می‌گیرد ولی نسبت پهنا و عرض را حفظ کرده ولی قسمت هایی از تصویر در کنترل دیده نمی‌شود.

همانطور که قبلا هم گفتیم، خود کنترل دکمه شامل زیر کنترل‌هایی می‌شود که یکی از آن‌ها TextBlock است و از طریق خصوصیت *.TextBlock دیگر خصوصیات آن قابل تنظیم است و البته برای خصوصی سازی بیشتر هم می‌توان یک TextBlock را به صورت Nested یعنی داخل تگ Button تعریف کنید که ما همین کار را کرده ایم.
فرم نهایی ما به صورت زیر است:


در صورتی که دوست دارید جهت ListBox را از عمودی به افقی تغییر دهید می‌توانید از پنل‌های Stack یا Wrap استفاده کنید که تعریف آن به شکل زیر است:
<ListBox>
    <ListBox.ItemsPanel>
        <ItemsPanelTemplate>
            <VirtualizingStackPanel Orientation="Horizontal" />
        </ItemsPanelTemplate>
    </ListBox.ItemsPanel>
</ListBox>

بدین صورت ListBox به شکل زیر تغییر می‌یابد:



و در صورتی که می‌خواهید Scroll حذف شود و از Wrap استفاده کنید، کد را به شکل زیر تعریف کنید. فراموش نکنید که اسکرول افقی را غیرفعال کنید؛ وگرنه نتیجه کار به شکل بالا خواهد بود.
<ListBox ScrollViewer.HorizontalScrollBarVisibility="Disabled">
    <ListBox.ItemsPanel>
        <ItemsPanelTemplate>
            <WrapPanel />
        </ItemsPanelTemplate>
    </ListBox.ItemsPanel>
</ListBox>
نتیجه:

Calendar
تقویم یکی دیگر از کنترل‌های موجود است که شامل خصوصیات زیر است:
DisplayDate: تاریخ پیش فرض و اولیه تقویم را مشخص می‌کند؛ در صورتی که ذکر نشود تاریخ جاری درج می‌شود.
<Calendar DisplayDate="01.01.2010" />
از خصوصیات دیگر در این زمینه می‌توان به DisplayDateStart و DisplayDateEnd اشاره کرد که محدوه‌ی نمایش تاریخ تقویم را مشخص می‌کند. کد زیر تنها تاریخ‌های روز اول ماه ابتدای سال 2015، تا روز اول ماه پنجم 2015 را نمایش می‌دهد:
<Calendar DisplayDateStart="01.01.2015" DisplayDateEnd="05.01.2015" />


SelectionMode: نحوه‌ی انتخاب تاریخ را مشخص می‌کند:
SingleDate: فقط یک تاریخ قابل انتخاب است.
SingleRange: می‌توانید از یک تاریخ تا تاریخ دیگر را انتخاب کنید. ولی نمی‌توانید مجددا چند انتخاب دیگر را در جای جای تقویم داشته باشید. مثلا از تاریخ 5 آپریل تا 10 آپریل را انتخاب کرده‌اید؛ ولی دیگر نمی‌توانید تاریخ 15 آپریل یا محدوده‌ی 15 آپریل تا 20 آپریل را انتخاب کنید. چون تنها قادر به انتخاب یک رنج یا محدوده تاریخی هستید.
MultipleRanges: بر خلاف گزینه‌ی بالایی هر محدوده تاریخی قابل انتخاب است.
<Calendar SelectionMode="MultipleRange" />

نکته بعدی در مورد غیرفعال کردن بعضی از تاریخ هاست که شما قصد ندارید به کاربر اجازه دهید آن‌ها را انتخاب کند. برای مثال تاریخ‌های 1 آپریل تا 10 آپریل را از دسترس خارج کنید. برای همین از خصوصیت BlackoutDates استفاده می‌کنیم که نحوه‌ی تعریف آن به شرح زیر است که در این کد دو محدوده‌ی تاریخی غیر فعال شده اند:
<Calendar>
    <Calendar.BlackoutDates>
        <CalendarDateRange Start="01/01/2010" End="01/06/2010" />
        <CalendarDateRange Start="05/01/2010" End="05/03/2010" />
    </Calendar.BlackoutDates>
</Calendar>



DisplayMode
: به طور پیش فرض، تقویم ماه‌ها را نشان می‌دهد ولی میتوانید آن را توسط این خصوصیت روی سال یا دهه و ماه هم تنظیم کنید.
با انتخاب سال Year، تقویم ماه‌های یک سال را نمایش می‌دهد.
با انتخاب دهه Decades سال‌های یک دهه‌ی تعیین شده را نشان می‌دهد و با انتخاب ماه Month روز‌های هر ماه در آن نمایش داده می‌شود.
در هنگام انتخاب این گزینه، به داخل تقویم نگاه نکنید، بلکه به سر تیتر آن نگاه کنید.
<Calendar DisplayMode="Year" />