This library provides a fluent interface for creating terse and expressive tests against ASP.NET MVC controllers. This library is part of TestStack .
اشتراکها
ScriptDOM سورس باز شد
اشتراکها
کتاب رایگان UWP Succinctly
Modern Microsoft is much more than Windows, and to reach their full potential developers must be able to reach users on the many platforms they use. To facilitate this, Microsoft created Universal Windows Platform (UWP) to make development across multiple platforms simultaneously an achievable goal. In UWP Succinctly, the first part of a series, author Matteo Pagani guides readers towards developing their own UWP applications.
Table of Contents
- Introduction
- The Essential Concepts: Visual Studio, XAML, and C#
- Creating the User Interface: The Controls
اشتراکها
ReadyRoll محصول جدید شرکت Red-Gate
Develop and deploy databases in Visual Studio with migration scripts.
Want to work on databases in Visual Studio alongside your application? Feeling the pain of managing and deploying database changes manually? Then ReadyRoll's the tool for you.
It generates numerically ordered SQL migration scripts that sit inside your project and take your schema from one version to the next.
You can add them to version control, use them to build and release, and automate database and application deployments, all in one process.
فرض کنید لیست حروف الفبای فارسی را در یک بانک اطلاعاتی SQLite ذخیره کردهاید:
اگر از این لیست کوئری گرفته و آنهارا مرتب کنیم:
یک چنین خروجی حاصل میشود:
همانطور که ملاحظه میکنید، مرتب سازی حروف فارسی در اینجا به صورت پیشفرض کار نمیکند. علت اینجا است که روش پیشفرض مرتب سازی حروف در SQLite، بر اساس کد اسکی حروف است و فقط در مورد حروف ASCII از A تا Z درست کار میکند.
امکان تعریف Collation سفارشی در SQLite
در بانکهای اطلاعاتی، قابلیتی که مستقیما بر روی نحوهی جستجو و همچنین مرتب سازی حروف تاثیر میگذارد، Collation نام دارد و در SQLite برخلاف بسیاری از بانکهای اطلاعاتی دیگر، امکان تعریف Collation سفارشی نیز وجود دارد و برای این منظور باید یک function pointer را در اختیار آن قرار داد تا از آن در سمت بانک اطلاعاتی جهت مرتب سازی و جستجوی حروف استفاده کند.
خوشبختانه پروژهی Microsoft.Data.Sqlite امکان تبدیل یک managed delegate دات نتی را به یک function pointer مخصوص SQLite، میسر میکند. به عبارتی SQLite کدهای دات نتی را در حین انجام محاسبات خود اجرا خواهد کرد و این اجرا به صورتی نیست که ابتدا کل اطلاعات، به سمت برنامهی کلاینت منتقل شود و سپس در این سمت، در حافظه، عملیاتی بر روی آن صورت گیرد. کل عملیات در سمت بانک اطلاعاتی مدیریت میشود.
روش تعریف یک Collation جدید هم در اینجا بسیار سادهاست:
فقط کافی است بر روی اتصال باز شده، متد CreateCollation فراخوانی و نحوهی مقایسهی دو رشته مشخص شود. سپس این Collation نامدار، به صورت زیر در کوئریها قابل استفاده خواهد بود:
اینبار اگر خروجی برنامه را بررسی کنیم، مشاهده خواهیم کرد که مرتب سازی حروف فارسی در SQLite به درستی کار میکند:
تعریف Collation سفارشی غیرحساس به «ی و ک» !
این مورد شاید یکی از آرزوهای توسعه دهندگان SQL Server باشد! اما با SQLite به سادگی زیر قابل تعریف و مدیریت است:
متد ApplyCorrectYeKe فوق از بستهی نیوگت DNTPersianUtils.Core دریافت شده و کار آن یکدست کردن «ی و ک» فارسی و عربی است.
در یک چنین حالتی اگر اطلاعاتی را به همراه «ی و ک» فارسی و یا عربی ثبت کنیم:
جستجوی بر روی آنها دیگر وابستهی به مقدار «ی و ک» وارد شده نبوده و چه «ی و ک» فارسی وارد شود و چه عربی، این کوئری همواره کار میکند:
کدهای کامل این مثال را از اینجا میتوانید دریافت کنید: SQLitePersianCollation.zip
var connection = new SqliteConnection("Data Source=:memory:"); connection.Open(); var createCommand = connection.CreateCommand(); createCommand.CommandText = @" CREATE TABLE persian_letter ( value TEXT ); INSERT INTO persian_letter VALUES ('ا'),('ب'),('پ'),('ت'),('ث'),('ج'),('چ'),('ح'),('خ'),('د'),('ذ'),('ر'),('ز'),('ژ'),('س'),('ش'), ('ص'),('ض'),('ط'),('ظ'),('ع'),('غ'),('ف'),('ق'),('ک'),('گ'),('ل'),('م'),('ن'),('و'),('ه'),('ی'); "; createCommand.ExecuteNonQuery();
var queryCommand = connection.CreateCommand(); queryCommand.CommandText = @" SELECT value FROM persian_letter order by value "; var reader = queryCommand.ExecuteReader(); var sortedDbItems = new List<string>(); while (reader.Read()) { sortedDbItems.Add($"{reader["value"]}"); }
همانطور که ملاحظه میکنید، مرتب سازی حروف فارسی در اینجا به صورت پیشفرض کار نمیکند. علت اینجا است که روش پیشفرض مرتب سازی حروف در SQLite، بر اساس کد اسکی حروف است و فقط در مورد حروف ASCII از A تا Z درست کار میکند.
امکان تعریف Collation سفارشی در SQLite
در بانکهای اطلاعاتی، قابلیتی که مستقیما بر روی نحوهی جستجو و همچنین مرتب سازی حروف تاثیر میگذارد، Collation نام دارد و در SQLite برخلاف بسیاری از بانکهای اطلاعاتی دیگر، امکان تعریف Collation سفارشی نیز وجود دارد و برای این منظور باید یک function pointer را در اختیار آن قرار داد تا از آن در سمت بانک اطلاعاتی جهت مرتب سازی و جستجوی حروف استفاده کند.
خوشبختانه پروژهی Microsoft.Data.Sqlite امکان تبدیل یک managed delegate دات نتی را به یک function pointer مخصوص SQLite، میسر میکند. به عبارتی SQLite کدهای دات نتی را در حین انجام محاسبات خود اجرا خواهد کرد و این اجرا به صورتی نیست که ابتدا کل اطلاعات، به سمت برنامهی کلاینت منتقل شود و سپس در این سمت، در حافظه، عملیاتی بر روی آن صورت گیرد. کل عملیات در سمت بانک اطلاعاتی مدیریت میشود.
روش تعریف یک Collation جدید هم در اینجا بسیار سادهاست:
connection.CreateCollation("PersianCollationNoCase", (x, y) => string.Compare(x, y, ignoreCase: true));
SELECT value FROM persian_letter order by value COLLATE PersianCollationNoCase
تعریف Collation سفارشی غیرحساس به «ی و ک» !
این مورد شاید یکی از آرزوهای توسعه دهندگان SQL Server باشد! اما با SQLite به سادگی زیر قابل تعریف و مدیریت است:
connection.CreateCollation("PersianCollationNoCaseYekeInsensitive", (x, y) => string.Compare(x.ApplyCorrectYeKe(), y.ApplyCorrectYeKe(), ignoreCase: true));
در یک چنین حالتی اگر اطلاعاتی را به همراه «ی و ک» فارسی و یا عربی ثبت کنیم:
CREATE TABLE persian_letter ( value TEXT ); INSERT INTO persian_letter VALUES ('ی'),('ک');
SELECT count() FROM persian_letter WHERE value = 'ی' COLLATE PersianCollationNoCaseYekeInsensitive
کدهای کامل این مثال را از اینجا میتوانید دریافت کنید: SQLitePersianCollation.zip
اشتراکها
انتشار سورس #Unity C
در قسمت قبل یادگرفتیم که چگونه GraphQL را با ASP.NET Core یکپارچه کنیم و اولین GraphQL query را ایجاد و دادهها را از سرور بازیابی کردیم. البته ما به این query های ساده بسنده نخواهیم کرد. در این قسمت میخواهیم یاد بگیریم که چگونه query های پیشرفتهی GraphQL را بنویسیم و در زمان انجام این کار، نمایش دهیم که چگونه خطاها را مدیریت کنیم و علاوه بر این با queries, aliases, arguments, fragments نیز کار خواهیم کرد.
Creating Complex Types for GraphQL Queries
اگر نگاهی به owners و query (در پایان قسمت قبل) بیندازیم، متوجه خواهیم شد که یک لیست از خصوصیات مدل Owner که در OwnerType معرفی شدهاند، نسبت به کوئری برگشت داده میشود. OwnerType شامل فیلدهای Id , Name و Address میباشد. یک Owner میتواند چندین account مرتبط با خود را داشته باشد. هدف این است که در owners ،query لیست account های مربوط به هر owner را بازگشت دهیم.
قبل از اضافه کردن فیلد Accounts در کلاس OwnerType نیاز است کلاس AccountType را ایجاد کنیم. در ادامه یک کلاس را به نام AccountType در پوشه GraphQLTypes ایجاد میکنیم.
public class AccountType : ObjectGraphType<Account> { public AccountType() { Field(x => x.Id, type: typeof(IdGraphType)).Description("Id property from the account object."); Field(x => x.Description).Description("Description property from the account object."); Field(x => x.OwnerId, type: typeof(IdGraphType)).Description("OwnerId property from the account object."); } }
همانطور که مشخص است، خصوصیت Type را از کلاس Account، معرفی نکردهایم (در ادامه اینکار را انجام خواهیم داد). در ادامه، واسط IAccountRepository و کلاس AccountRepository را باز کرده و آن را مطابق زیر ویرایش میکنیم:
public interface IAccountRepository { IEnumerable<Account> GetAllAccountsPerOwner(Guid ownerId); } public class AccountRepository : IAccountRepository { private readonly ApplicationContext _context; public AccountRepository(ApplicationContext context) { _context = context; } public IEnumerable<Account> GetAllAccountsPerOwner(Guid ownerId) => _context.Accounts .Where(a => a.OwnerId.Equals(ownerId)) .ToList(); }
اکنون میتوان لیست accountها را به نتیجه owners ، query اضافه کنیم. پس کلاس OwnerType را باز کرده و آن را مطابق زیر ویرایش میکنیم:
public class OwnerType : ObjectGraphType<Owner> { public OwnerType(IAccountRepository repository) { Field(x => x.Id, type: typeof(IdGraphType)).Description("Id property from the owner object."); Field(x => x.Name).Description("Name property from the owner object."); Field(x => x.Address).Description("Address property from the owner object."); Field<ListGraphType<AccountType>>( "accounts", resolve: context => repository.GetAllAccountsPerOwner(context.Source.Id) ); } }
چیز خاصی در اینجا وجود ندارد که ما تا کنون ندیده باشیم. به همان روش که یک فیلد را در کلاس AppQuery ایجاد کردیم، یک فیلد را با نام accounts در کلاس OwnerType ایجاد میکنیم. همچنین متد GetAllAccountsPerOwner نیاز به پارامتر id را دارد و این پارامتر را از طریق context.Source.Id فراهم میکنیم. زیرا context شامل خصوصیت Source است که در این حالت مشخص نوع Owner میباشد.
اکنون پروژه را اجرا کنید و به آدرس زیر بروید:
https://localhost:5001/ui/playground
{ owners{ id, name, address, accounts{ id, description, ownerId } } }
Adding Enumerations in GraphQL Queries
در کلاس AccountType فیلد Type را اضافه نکردهایم و این کار را عمدا انجام دادهایم. اکنون زمان انجام این کار میباشد. برای اضافه کردن گونه شمارشی به کلاس AccountType نیاز است تا در ابتدا یک کلاس تعریف شود که نسبت به type های معمول در GraphQL متفاوت است. یک کلاس را به نام AccountTypeEnumType در پوشه GraphQLTypes ایجاد کرده و آن را مطابق زیر ویرایش میکنیم:
public class AccountTypeEnumType : EnumerationGraphType<TypeOfAccount> { public AccountTypeEnumType() { Name = "Type"; Description = "Enumeration for the account type object."; } }
کلاس AccountTypeEnumType باید از نوع جنریک کلاس EnumerationGraphType ارث بری کند و پارامتر جنریک آن، یک گونه شمارشی را دریافت میکند (که در قسمت قبل آن را ایجاد کردیم؛ TypeOfAccount). همچنین مقدار خصوصیت Name نیز باید همان نام خصوصیت گونه شمارشی در کلاس Account باشد (نام آن در کلاس Account مساوی Type میباشد). سپس گونه شمارشی را در کلاس AccountType به صورت زیر اضافه میکنیم:
public class AccountType : ObjectGraphType<Account> { public AccountType() { ... Field<AccountTypeEnumType>("Type", "Enumeration for the account type object."); } }
اکنون پروژه را اجرا کنید و سپس owners ، query را در UI.Playground به صورت زیر اجرا کنید:
{ owners{ id, name, address, accounts{ id, description, type, ownerId } } }
که نتیجه آن اضافه شدن type به هر account میباشد:
Implementing a Cache in the GraphQL Queries with Data Loader
دیدم که query، نتیجه دلخواهی را برای ما بازگشت میدهد؛ اما این query هنوز به اندازه کافی بهینه نشدهاست. مشکل چیست؟
query ایجاد شده به حالتی کار میکند که در ابتدا همه owner ها را بازیابی میکند. سپس به ازای هر owner، یک Sql Query را به سمت بانک اطلاعاتی ارسال میکند تا Account های مربوط به آن Owner را بازگشت دهد که میتوان log آن را در Terminal مربوط به VS Code مشاهده کرد.
البته زمانیکه چند موجودیت owner را داشته باشیم، این مورد یک مشکل نمیباشد؛ ولی وقتی تعداد موجودیتها زیاد باشد چطور؟
owners ، query را میتوان با استفاده از DataLoader که توسط GraphQL فراهم شدهاست، بهینه سازی کرد. جهت انجام اینکار در ابتدا واسط IAccountRepository و کلاس AccountRepository را همانند زیر ویرایش میکنیم:
public interface IAccountRepository { ... Task<ILookup<Guid, Account>> GetAccountsByOwnerIds(IEnumerable<Guid> ownerIds); }
public class AccountRepository : IAccountRepository { ... public async Task<ILookup<Guid, Account>> GetAccountsByOwnerIds(IEnumerable<Guid> ownerIds) { var accounts = await _context.Accounts.Where(a => ownerIds.Contains(a.OwnerId)).ToListAsync(); return accounts.ToLookup(x => x.OwnerId); } }
نیاز است که یک متد داشته باشیم که <<Task<ILookup<TKey, T را برگشت میدهد؛ زیرا DataLoader نیازمند یک متد با نوع برگشتی که در امضایش عنوان شده است میباشد .
در ادامه کلاس OwnerType را مطابق زیر ویرایش میکنیم:
public class OwnerType : ObjectGraphType<Owner> { public OwnerType(IAccountRepository repository, IDataLoaderContextAccessor dataLoader) { ... Field<ListGraphType<AccountType>>( "accounts", resolve: context => { var loader = dataLoader.Context.GetOrAddCollectionBatchLoader<Guid, Account>("GetAccountsByOwnerIds", repository.GetAccountsByOwnerIds); return loader.LoadAsync(context.Source.Id); }); } }
در کلاس OwnerType، واسط IDataLoaderContextAccessor را در سازنده کلاس تزریق میکنیم و سپس متد Context.GetOrAddCollectionBatchLoader را فراخوانی میکنیم که در پارامتر اول آن، یک کلید و در پارامتر دوم آن، متد GetAccountsByOwnerIds را از IAccountRepository معرفی میکنیم.
سپس باید DataLoader را در متد ConfigureServices موجود در کلاس Startup ثبت کنیم. در ادامه services.AddGraphQL را مطابق زیر ویرایش میکنیم:
services.AddGraphQL(o => { o.ExposeExceptions = false; }) .AddGraphTypes(ServiceLifetime.Scoped) .AddDataLoader();
اکنون پروژه را با دستور زیر اجرا کنید و سپس query قبلی را در UI.Playground اجرا کنید.
اگر log موجود در Terminal مربوط به VS Code را مشاهده کنید، متوجه خواهید شد که در این حالت یک query برای تمام owner ها و یک query برای تمام account ها داریم.
Using Arguments in Queries and Handling Errors
تا کنون ما یک query را اجرا میکردیم که نتیجه آن بازیابی تمام owner ها به همراه تمام account های مربوط به هر owner بود. اکنون میخواهیم براساس id، یک owner مشخص را بازیابی کنیم. برای انجام این کار نیاز است که یک آرگومان را در query شامل کنیم.
در ابتدا واسط IOwnerRepository و کلاس OwnerRepository را همانند زیر ویرایش میکنیم:
public interface IOwnerRepository { ... Owner GetById(Guid id); } public class OwnerRepository : IOwnerRepository { ... Owner GetById(Guid id) => _context.Owners.SingleOrDefault(o => o.Id.Equals(id)); }
public class AppQuery : ObjectGraphType { public AppQuery(IOwnerRepository repository) { ... Field<OwnerType>( "owner", arguments: new QueryArguments(new QueryArgument<NonNullGraphType<IdGraphType>> { Name = "ownerId" }), resolve: context => { var id = context.GetArgument<Guid>("ownerId"); return repository.GetById(id); } ); } }
در اینجا یک فیلد را ایجاد کردهایم که مقدار برگشتی آن یک OwnerType میباشد. نام query را owner تعیین میکنیم و از بخش arguments، برای ایجاد کردن آرگومانهای این query استفاده میکنیم. آرگومان این query نمیتواند NULL باشد و باید از نوع IdGraphType و با نام ownerId باشد و در نهایت بخش resolve است که کاملا گویا میباشد.
اگر پارامتر id، از نوع Guid نباشد، بهتر است که یک پیام را به سمت کلاینت برگشت دهیم. جهت انجام این کار یک اصلاح کوچک در بخش resolve انجام میدهیم:
Field<OwnerType>( "owner", arguments: new QueryArguments(new QueryArgument<NonNullGraphType<IdGraphType>> { Name = "ownerId" }), resolve: context => { Guid id; if (!Guid.TryParse(context.GetArgument<string>("ownerId"), out id)) { context.Errors.Add(new ExecutionError("Wrong value for guid")); return null; } return repository.GetById(id); } );
اکنون پروژه را اجرا کنید و سپس یک query جدید را در UI.Playground به صورت زیر ارسال کنید:
{ owner(ownerId:"6f513773-be46-4001-8adc-2e7f17d52d83"){ id, name, address, accounts{ id, description, type, ownerId } }
نکته:
در صورتیکه قصد داشته باشیم علاوه بر id، یک name را هم ارسال کنیم، در بخش resolve به صورت زیر آن را دریافت میکنیم:
string name = context.GetArgument<string>("name");
{ owner(ownerId:"53270061-3ba1-4aa6-b937-1f6bc57d04d2", name:"ANDY") { ... } }
Aliases, Fragments, Named Queries, Variables, Directives
می توانیم برای query های ارسال شده از سمت کلاینت با معرفی aliases، یک سری تغییرات را داشته باشیم. وقتیکه میخواهیم نام نتیجه دریافتی یا هر فیلدی را در نتیجه دریافتی تغییر دهیم، بسیار کاربردی میباشند. اگر یک query داشته باشیم که یک آرگومان را دارد و بخواهیم دو تا از این query داشته باشیم، برای ایجاد تفاوت بین query ها میتوان از aliases استفاده کرد.
جهت استفاده باید نام مورد نظر را در ابتدای query یا فیلد قرار دهیم:
{ first:owners{ ownerId:id, ownerName:name, ownerAddress:address, ownerAccounts:accounts { accountId:id, accountDescription:description, accountType:type } }, second:owners{ ownerId:id, ownerName:name, ownerAddress:address, ownerAccounts:accounts { accountId:id, accountDescription:description, accountType:type } } }
همانطور که از مثال بالا مشخص است، دو query با فیلدهای یکسانی را داریم. اگر بجای 2 query یکسان (مانند مثال بالا) ولی با آرگومانهای متفاوت، اینبار 10 query یکسان با آرگومانهای متفاوتی را داشته باشیم، در این حالت خواندن query ها مقداری سخت میباشد. در این صورت میتوان این مشکل را با استفاده از fragmentها برطرف کرد. Fragmentها این اجازه را به ما میدهند تا فیلدها را با استفاده از کاما ( ، ) از یکدیگر جدا و تبدیل به یک بخش مجزا کنیم و سپس استفاده مجدد از آن بخش را در تمام query ها داشته باشیم. Syntax آن به حالت زیر میباشد:
fragment SampleName on Type{ ... }
تعریف یک fragment به نام ownerFields و استفاده از آن :
{ first:owners{ ...ownerFields }, second:owners{ ...ownerFields }, ... } fragment ownerFields on OwnerType{ ownerId:id, ownerName:name, ownerAddress:address, ownerAccounts:accounts { accountId:id, accountDescription:description, accountType:type } }
برای ایجاد کردن یک named query، مجبور هستیم از کلمه کلیدی query در آغاز کل query استفاده کنیم؛ به همراه نام query، که بعد از کلمه کلیدی query قرار میگیرد. اگر نیاز داشته باشیم میتوان آرگومانها را به query ارسال کرد.
نکته مهمی که در رابطه با named query ها وجود دارد این است که اگر یک query آرگومان داشته باشد نیاز است از پنجره QUERY VARIABLES برای تخصیص مقدار به آن آرگومان استفاده کنیم.
query OwnerQuery($ownerId:ID!) { owner(ownerId:$ownerId){ id, name, address, accounts{ id, description, type } } }
{ "ownerId":"6f513773-be46-4001-8adc-2e7f17d52d83" }
اکنون اجرا کنید و خروجی را مشاهده کنید .
در نهایت میتوان بعضی فیلدها را از نتیجه دریافتی با استفاده از directiveها در query حذف یا اضافه کرد. دو directive وجود دارد که میتوان از آنها استفاده کرد (include و skip).
در قسمت بعد در رابطه با GraphQL Mutations صحبت خواهیم کرد.
کدهای مربوط به این قسمت را از اینجا دریافت کنید . ASPCoreGraphQL_2.zip
اشتراکها
بررسی بدافزار داتنتی Async RAT
Dissecting Async RAT — . Net based Malware — Malware Analysis
AsyncRAT is a Remote Access Tool (RAT) designed to remotely monitor and control other computers through a secure encrypted connection.
It is an open source remote administration tool, however, it could also be used maliciously because it provides functionality such as keylogger, remote desktop control, and many other functions that may cause harm to the victim’s computer.