This tough year, 2020, will soon be over at last, which means it's time to look back at our accomplishments! Over the year, the PVS-Studio team has written quite a number of articles covering a large variety of bugs found in open-source projects with the help of PVS-Studio. This 2020 Top-10 list of bugs in C# projects presents the most interesting specimens. Enjoy the reading!
فعال سازی عملیات CRUD در Kendo UI Grid
دیتای زیر را در نظر بگیرید:
[ { OrderId: 1, OrderName: 'order 1' OrderItems: [ { ProductId: 1, ProductName: "sample name" }, { ProductId: 2, ProductName: "sample name 2" } }, { OrderId: 2, OrderName: 'order 2' OrderItems: [ { ProductId: 55, ProductName: "sample name 55" }, { ProductId: 18, ProductName: "sample name18" } } ]
من چطور میتونم مدلی تعریف کنم که بتوان مدل های nested این مدل رو پوشش بده:
var model = kendo.data.Model.define({ id: "OrderId", fields: { OrderId: { type: "number", editable: false }, OrderName: { type: "string", editable: false }, OrderItems: { ?????????????? } } });
آیا امکان تعریف مدلی به این شکل وجود دارد که بتوان در حین عملیات CRUD مدل های nested را هم تغییر داد؟
ILSpy 7.0 منتشر شد
New Language Features
- C# 9.0: record classes
- C# 9.0: with expressions
- C# 9.0: primary constructors
- Support for .NET 5 custom calling conventions
- Improved support for Unsafe-intrinsics
UI Improvements
- Dark mode (@tom-englert in #2347)
- .NET bundles and Nuget packages are now directly embedded in the tree view
- Search enabled in NuGet packages
- Added setting highlight the current line in the code view (see #2224, by @DickvdBrink)
- Simple UI language switching support
معرفی System.Text.Json در NET Core 3.0.
{"$.language":["The JSON value could not be converted to DNTCaptcha.Core.Providers.Language. Path: $.language | LineNumber: 0 | BytePositionInLine: 99."]}
if (model == null || !ModelState.IsValid) { return BadRequest(ModelState); }
services.AddControllers().AddJsonOptions(opt => { opt.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter()); });
ما در AngularJs آبجکتی را به نام q$ داریم که برای اجرای توابع به صورت async مفید است و همچنین در استفاده از مقادیر برگشتی از این درخواستها برای پردازشهای آینده به ما کمک میکند. برای اطلاعات بیشتر در مورد این سرویس به اینجا مراجعه کنید.
در ادامه ما از تابع ()all از q$ برای ترکیب چند شیء promise داخل یک شیء promise، به منظور صدا زدن چند سرویس به صورت یکجا، استفاده میکنیم.
پیاده سازی ASP.NET Web API
قدم اول : Visual Studio را بازکنید و یک پروژه empty ASP.NET Web API را مطابق شکل زیر ایجاد کنید.
using System.Collections.Generic; namespace NG_Combine_Multiple_Promises.Models { public class Courses { public int CourseId { get; set; } public string CourseName { get; set; } } public class CourseDatabase : List<Courses> { public CourseDatabase() { Add(new Courses() { CourseId=1,CourseName="الکترونیک"}); Add(new Courses() { CourseId = 2, CourseName = "ریاضی 2" }); Add(new Courses() { CourseId = 3, CourseName = "طراحی نرم افزار" }); } } public class Student { public int StudentId { get; set; } public string Name { get; set; } public string AcadmicYear { get; set; } } public class StudentDatabase : List<Student> { public StudentDatabase() { Add(new Student() {StudentId=101,Name="محمد علوی", AcadmicYear="اول" }); Add(new Student() { StudentId = 102, Name = "طاهره موسوی", AcadmicYear = "دوم" }); Add(new Student() { StudentId = 103, Name = "علی عباسی", AcadmicYear = "سوم" }); Add(new Student() { StudentId = 104, Name = "جواد نوری", AcadmicYear = "اول" }); Add(new Student() { StudentId = 105, Name = "محسن خدایی", AcadmicYear = "دوم" }); Add(new Student() { StudentId = 106, Name = "علی کاظمی", AcadmicYear = "سوم" }); Add(new Student() { StudentId = 107, Name = "زهرا مقدم", AcadmicYear = "اول" }); Add(new Student() { StudentId = 108, Name = "لاله فکور", AcadmicYear = "دوم" }); Add(new Student() { StudentId = 109, Name = "علی نوروزی", AcadmicYear = "چهارم" }); } } }
کد بالا شامل موجودیتهای Courses و Student است و کلاسهای CourseDatabase و StudentDatabase به ترتیب برای ذخیره این موجودیتها است.
قدم سوم : بستهی نیوگت Microsoft.AspNet.WebAPi.Cors را با استفاده از NuGet Package Manager، به منظور فعال سازی امکان صدا زدن این وب سرویس از دامنههای مختلف، به پروژه اضافه کرده و کد زیر را در کلاس WebApiCofig در پوشه App_Start قرار دهید.
config.EnableCors();
قدم چهارم : دو کنترل از نوع Web API 2 Empty را با نامهای CourseAPIController و StudentAPIController ایجاد کرده و کدهای زیر را در آنها قرار دهید.
CourseAPIController.cs
[EnableCors("*","*","*")] public class CourseAPIController : ApiController { [Route("Courses")] public IEnumerable<Courses> Get() { return new CourseDatabase(); } }
StudentAPIController.cs
[EnableCors("*", "*", "*")] public class StudentAPIController : ApiController { [Route("Students")] public IEnumerable<Student> Get() { return new StudentDatabase(); } }
استفاده از Angular $q.all
قدم اول : پروژهی جدیدی را از نوع Empty ASP.NET در همین solution اضافه کرده و ارجاعات jQuery, Bootstrap و AngularJS را با استفاده از NuGet Package manager مانند زیر اضافه کنید:
Install-Package jQuery Install-Package bootstrap Install-Package angularjs
قدم دوم : پوشهایی را به نام MyScripts ایجاد کرده و درون آن فایل javascript زیر را با نام logic.js اضافه کنید:
var app = angular.module('mymodule', []); //سرویسی برای بازگرداندن لیست دروس app.service('courseService', function ($http) { this.get = function () { var response = $http.get("http://localhost:11696/Courses"); return response; }; }); //سرویسی برای بازگرداندن لیست دانشجویان app.service('studentService', function ($http) { this.get = function () { var response = $http.get("http://localhost:11696/Students"); return response; }; }); //تعریف کنترلر app.controller('ctrl', function ($scope, $q, courseService, studentService) { $scope.Courses = []; $scope.Students = []; loadData(); /**/ function loadData() { var promiseCourse = courseService.get(); var promiseStudent = studentService.get(); $scope.combineResult = $q.all([ promiseCourse, promiseStudent ]).then(function (resp) { $scope.Courses = resp[0].data; $scope.Students = resp[1].data; }); } });
<!DOCTYPE html> <html ng-app="mymodule"> <head> <title></title> <meta charset="utf-8" /> <link href="../Content/bootstrap.min.css" rel="stylesheet" /> <script src="../Scripts/jquery-3.2.1.min.js"></script> <script src="../Scripts/angular.min.js"></script> <script src="../MyScripts/logic.js"></script> </head> <body ng-controller="ctrl"> <div class="container"> <h1 class="h1">دروس</h1> <table class="table table-striped table-bordered table-condensed"> <thead> <tr> <td class="text-center">کد درس</td> <td class="text-center">نام درس</td> </tr> </thead> <tbody> <tr ng-repeat="Course in Courses"> <td class="text-center">{{Course.CourseId}}</td> <td class="text-center">{{Course.CourseName}}</td> </tr> </tbody> </table> <hr /> <h1 class="h1">دانشجویان</h1> <table class="table table-striped table-bordered table-condensed"> <thead> <tr> <td class="text-center">کد دانشجویی</td> <td class="text-center">نام و نام خانوادگی</td> <td class="text-center">سال دانشجویی</td> </tr> </thead> <tbody> <tr ng-repeat="Student in Students"> <td class="text-center">{{Student.StudentId}}</td> <td class="text-center">{{Student.Name}}</td> <td class="text-center">{{Student.AcadmicYear}}</td> </tr> </tbody> </table> </div> </body> </html>
مدتی پیش نیاز پیدا کردم تا فراخوانی متدهایی را 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"));
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
ایجاد کاتالوگهای Full text search و ایندکسهای آن
همانطور که در قسمت قبل نیز عنوان شد، فیلترهای FTS آفیس، علاوه بر اینکه امکان جستجوی پیشرفته FTS را بر روی کلیه فایلهای مجموعه آفیس میسر میکنند، امکان جستجوی FTS را بر روی خواص ویژه اضافی آنها، مانند نام نویسنده، واژههای کلیدی، تاریخ ایجاد و امثال آن نیز به همراه دارند.
اینکه چه خاصیتی را بتوان جستجو کرد نیز بستگی به نوع فیلتر نصب شده دارد. برای تعریف خواص قابل جستجوی یک سند، باید یک SEARCH PROPERTY LIST را ایجاد کرد:
CREATE SEARCH PROPERTY LIST WordSearchPropertyList; GO ALTER SEARCH PROPERTY LIST WordSearchPropertyList ADD 'Authors' WITH (PROPERTY_SET_GUID = 'F29F85E0-4FF9-1068-AB91-08002B27B3D9', PROPERTY_INT_ID = 4, PROPERTY_DESCRIPTION = 'System.Authors - authors of a given item.'); GO
بهبود کیفیت جستجو توسط Stop lists و Stop words
به یک سری از کلمات و حروف، اصطلاحا noise words گفته میشود. برای مثال در زبان انگلیسی حروف و کلماتی مانند a، is، the و and به صورت خودکار از FTS حذف میشوند؛ چون جستجوی آنها بیحاصل است. به اینها stop words نیز میگویند.
با استفاده از کوئری ذیل میتوان لیست stop words تعریف شده در بانک اطلاعاتی جاری را مشاهده کرد:
-- Check the Stopwords list SELECT w.stoplist_id, l.name, w.stopword, w.language FROM sys.fulltext_stopwords AS w INNER JOIN sys.fulltext_stoplists AS l ON w.stoplist_id = l.stoplist_id;
-- Stopwords list CREATE FULLTEXT STOPLIST SQLStopList; GO -- Add a stopword ALTER FULLTEXT STOPLIST SQLStopList ADD 'SQL' LANGUAGE 'English'; GO
کاتالوگهای Full Text Search
ایندکسهای ویژهی FTS، در مکانهایی به نام Full Text Catalogs ذخیره میشوند. این کاتالوگها صرفا یک شیء مجازی بوده و تنها برای تعریف ظرفی دربرگیرندهی ایندکسهای FTS تعریف میشوند. در نگارشهای پیش از 2012 اس کیوال سرور، این کاتالوگها اشیایی فیزیکی بودند؛ اما اکنون تبدیل به اشیایی مجازی شدهاند.
حالت کلی تعریف یک fulltext catalog به نحو ذیل است:
create fulltext catalog catalog_name on filegroup filegroup_name in path 'rootpath' with some_options as default authoriztion owner_name accent_sensivity = {on|off}
به صورت پیش فرض حساسیت به لهجه یا accent_sensivity خاموش است. اگر روشن شود، باید کل ایندکس مجددا بازسازی شود.
ایجاد ایندکسهای Full Text
پس از ایجاد یک fulltext catalog، اکنون نوبت به تعریف ایندکسهایی فیزیکی هستند که داخل این کاتالوگها ذخیره خواهند شد:
-- Full-text catalog CREATE FULLTEXT CATALOG DocumentsFtCatalog; GO -- Full-text index CREATE FULLTEXT INDEX ON dbo.Documents ( docexcerpt Language 1033, doccontent TYPE COLUMN doctype Language 1033 STATISTICAL_SEMANTICS ) KEY INDEX PK_Documents ON DocumentsFtCatalog WITH STOPLIST = SQLStopList, SEARCH PROPERTY LIST = WordSearchPropertyList, CHANGE_TRACKING AUTO; GO
CHANGE_TRACKING AUTO به این معنا است که SQL Server به صورت خودکار کار به روز رسانی این ایندکس را با تغییرات رکوردها انجام خواهد داد.
ذکر STATISTICAL_SEMANTICS، منحصر به SQL Server 2012 بوده و کار آن تشخیص واژههای کلیدی و ایجاد ایندکسهای یافتن اسناد مشابه است. برای استفاده از آن حتما نیاز است مطابق توضیحات قسمت قبل، Semantic Language Database پیشتر نصب شده باشد.
توسط STOPLIST، لیست واژههایی که قرار نیست ایندکس شوند را معرفی خواهیم کرد. SQLStopList را در ابتدای بحث ایجاد کردیم.
Language 1033 به معنای استفاده از زبان US English است.
نحوهی استفاده از SEARCH PROPERTY LIST ایی که پیشتر تعریف کردیم را نیز در اینجا ملاحظه میکنید.
مثالی برای ایجاد ایندکسهای FTS
برای اینکه ربط منطقی نکات عنوان شده را بهتر بتوانید بررسی و آزمایش کنید، مثال ذیل را درنظر بگیرید.
ابتدا جدول Documents را برای ذخیره سازی تعدادی سند، ایجاد میکنیم:
CREATE TABLE dbo.Documents ( id INT IDENTITY(1,1) NOT NULL, title NVARCHAR(100) NOT NULL, doctype NCHAR(4) NOT NULL, docexcerpt NVARCHAR(1000) NOT NULL, doccontent VARBINARY(MAX) NOT NULL, CONSTRAINT PK_Documents PRIMARY KEY CLUSTERED(id) );
سپس اطلاعاتی را در این جدول ثبت میکنیم:
-- Insert data -- First row INSERT INTO dbo.Documents (title, doctype, docexcerpt, doccontent) SELECT N'Columnstore Indices and Batch Processing', N'docx', N'You should use a columnstore index on your fact tables, putting all columns of a fact table in a columnstore index. In addition to fact tables, very large dimensions could benefit from columnstore indices as well. Do not use columnstore indices for small dimensions. ', bulkcolumn FROM OPENROWSET (BULK 'C:\Users\Vahid\Desktop\Updates\fts_docs\ColumnstoreIndicesAndBatchProcessing.docx', SINGLE_BLOB) AS doc; -- Second row INSERT INTO dbo.Documents (title, doctype, docexcerpt, doccontent) SELECT N'Introduction to Data Mining', N'docx', N'Using Data Mining is becoming more a necessity for every company and not an advantage of some rare companies anymore. ', bulkcolumn FROM OPENROWSET (BULK 'C:\Users\Vahid\Desktop\Updates\fts_docs\IntroductionToDataMining.docx', SINGLE_BLOB) AS doc; -- Third row INSERT INTO dbo.Documents (title, doctype, docexcerpt, doccontent) SELECT N'Why Is Bleeding Edge a Different Conference', N'docx', N'During high level presentations attendees encounter many questions. For the third year, we are continuing with the breakfast Q&A session. It is very popular, and for two years now, we could not accommodate enough time for all questions and discussions! ', bulkcolumn FROM OPENROWSET (BULK 'C:\Users\Vahid\Desktop\Updates\fts_docs\WhyIsBleedingEdgeADifferentConference.docx', SINGLE_BLOB) AS doc; -- Fourth row INSERT INTO dbo.Documents (title, doctype, docexcerpt, doccontent) SELECT N'Additivity of Measures', N'docx', N'Additivity of measures is not exactly a data warehouse design problem. However, you have to realize which aggregate functions you will use in reports for which measure, and which aggregate functions you will use when aggregating over which dimension.', bulkcolumn FROM OPENROWSET (BULK 'C:\Users\Vahid\Desktop\Updates\fts_docs\AdditivityOfMeasures.docx', SINGLE_BLOB) AS doc; GO
fts_docs.zip
در ادامه میخواهیم قادر باشیم تا بر روی متادیتای نویسندهی این اسناد نیز جستجوی کامل FTS را انجام دهیم. به همین جهت SEARCH PROPERTY LIST آنرا نیز ایجاد خواهیم کرد:
-- Search property list CREATE SEARCH PROPERTY LIST WordSearchPropertyList; GO ALTER SEARCH PROPERTY LIST WordSearchPropertyList ADD 'Authors' WITH (PROPERTY_SET_GUID = 'F29F85E0-4FF9-1068-AB91-08002B27B3D9', PROPERTY_INT_ID = 4, PROPERTY_DESCRIPTION = 'System.Authors - authors of a given item.'); GO
-- Stopwords list CREATE FULLTEXT STOPLIST SQLStopList; GO -- Add a stopword ALTER FULLTEXT STOPLIST SQLStopList ADD 'SQL' LANGUAGE 'English'; GO
اکنون زمان ایجاد یک کاتالوگ FTS است:
-- Full-text catalog CREATE FULLTEXT CATALOG DocumentsFtCatalog; GO
و در آخر ایندکس FTS ایی را که پیشتر در مورد آن بحث کردیم، ایجاد خواهیم کرد:
-- Full-text index CREATE FULLTEXT INDEX ON dbo.Documents ( docexcerpt Language 1033, doccontent TYPE COLUMN doctype Language 1033 STATISTICAL_SEMANTICS ) KEY INDEX PK_Documents ON DocumentsFtCatalog WITH STOPLIST = SQLStopList, SEARCH PROPERTY LIST = WordSearchPropertyList, CHANGE_TRACKING AUTO; GO
در این تصویر محل یافتن اجزای مختلف Full text search را در management studio مشاهده میکنید.
یک نکتهی تکمیلی
برای زبان فارسی نیز یک سری stop words وجود دارند. لیست آنها را از اینجا میتوانید دریافت کنید:
stopwords.sql
متاسفانه زبان فارسی جزو زبانهای پشتیبانی شده توسط FTS در SQL Server نیست (نه به این معنا که نمیتوان با آن کار کرد؛ به این معنا که برای مثال دستورات صرفی زبان را ندارد) و به همین جهت از زبان انگلیسی در اینجا استفاده شدهاست.
PowerShell 7.x - قسمت سیزدهم - ساخت یک Static Site Generator ساده توسط PowerShell و GitHub Actions
├── _layout │ ├── _footer.html │ ├── _header.html │ ├── _nav.html │ └── main.html ├── build ├── img ├── posts └── set-posts.ps1
- دایرکتوری layout_: درون این دایرکتوری، ساختار اصلی بلاگ را قرار دادهایم. در ادامه محتویات هر فایل را مشاهده خواهید کرد:
<!--main.html--> <!DOCTYPE html> <html dir="rtl"> {{header}} <body> {{nav}} <main> {{content}} </main> {{footer}} </body> <!--_header.html--> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>{{title}}</title> <link href="https://cdn.jsdelivr.net/gh/rastikerdar/samim-font@v4.0.5/dist/font-face.css" rel="stylesheet" type="text/css" /> <link href="https://cdn.jsdelivr.net/npm/tailwindcss@2.2.16/dist/tailwind.min.css" rel="stylesheet"> <style> * { } </style> </head> <!--_nav.html--> <header> <nav> <div> <div> <a href="#">بلاگ من</a> </div> <div> <ul> {{nav}} </ul> </div> </div> </nav> </header> <!--_footer.html--> <footer> <div> <div> <p> تمامی حقوق محفوظ است </p> </div> </div> </footer>
- دایرکتوری build: درون این دایرکتوری، خروجیهای HTML که قرار است توسط اسکریپت PowerShell جنریت شوند، قرار خواهند گرفت. این پوشه در واقع قرار است توسط GitHub Pages میزبانی شود.
- دایرکتوری img: درون این دایرکتوری، تصاویر مربوط به هر بلاگپست را قرار خواهیم داد.
- دایرکتوری posts: درون این دایرکتوری، مطالبمان را با فرمت Markdown، قرار خواهیم داد. به عنوان مثال در ادامه یک نمونه از آن را مشاهده خواهید کرد (در کد زیر از Front Matter برای اضافه کردن یکسری متادیتای موردنیاز که حین بیلد شدن ضروری هستند استفاده شدهاست)
--- title: اولین پست من slug: hello date: 2023-04-26 author: سیروان عفیفی tags: [tag1, tag2, tag3] excerpt: این یک پست تستی است در مورد اینکه چطور میتوانیم از این قالب استفاده کنیم --- # اولین پست من ## اولین پست من ### اولین پست من #### اولین پست من ##### اولین پست من ###### اولین پست من لورم ایپسوم متن ساختگی با تولید سادگی نامفهوم از صنعت چاپ و با استفاده از طراحان گرافیک است. چاپگرها و متون بلکه روزنامه و مجله در ستون و سطرآنچنان که لازم است و برای شرایط فعلی تکنولوژی مورد نیاز و کاربردهای متنوع با هدف بهبود ابزارهای کاربردی میباشد. کتابهای زیادی در شصت و سه درصد گذشته، حال و آینده شناخت فراوان جامعه و متخصصان را میطلبد تا با نرم افزارها شناخت بیشتری را برای طراحان رایانه ای علی الخصوص طراحان خلاقی و فرهنگ پیشرو در زبان فارسی ایجاد کرد. در این صورت میتوان امید داشت که تمام و دشواری موجود در ارائه راهکارها و شرایط سخت تایپ به پایان رسد وزمان مورد نیاز شامل حروفچینی دستاوردهای اصلی و جوابگوی سوالات پیوسته اهل دنیای موجود طراحی اساسا مورد استفاده قرار گیرد. <img src="/img/graphql.jpg"/>
- فایل set_post.ps1: موتور اصلی جنریت کردن صفحات HTML این فایل میباشد. در ادامه محتویات آن را مشاهده خواهید کرد. سپس هر کدام از توابع استفاده شده را یکییکی توضیح خواهیم داد:
Function Get-Layouts { $headerLayout = Get-Content -Path ./_layout/_header.html -Raw $homeLayout = Get-Content -Path ./_layout/main.html -Raw $footerLayout = Get-Content -Path ./_layout/_footer.html -Raw Return @{ Header = $headerLayout Home = $homeLayout Footer = $footerLayout } } Function Get-PostFrontMatter($postContent) { $frontMatter = [regex]::Match($postContent, "(---(?:\r?\n(?!--|\s*$).*)*)\s*((?:\r?\n(?!---).*)*\r?\n---)") Return $frontMatter } Function Set-Headings($postHtml) { Return $postHtml -Replace '<h(\d) id="(.*)">', { $level = $_.Groups[1].Value $id = $_.Groups[2].Value $class = Switch ($level) { '1' { 'text-4xl font-bold mb-2' } '2' { 'text-3xl font-bold mb-2' } '3' { 'text-2xl font-bold mb-2' } '4' { 'text-xl font-bold mb-2' } '5' { 'text-lg font-bold mb-2' } '6' { 'text-base font-bold mb-2' } } "<h$level class='$class' id='$id'>" } } Function ConvertTo-Slug { [CmdletBinding()] param( [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [string]$String ) process { $slug = $String -replace '[^\w\s-]', '' # remove non-word characters except hyphens $slug = $slug -replace '\s+', '-' # replace whitespace with a single hyphen $slug = $slug -replace '^-+|-+$', '' # remove leading/trailing hyphens $slug = $slug.ToLower() # convert to lowercase Write-Output $slug } } Function Get-Posts { $markdownPosts = Get-ChildItem -Path ./posts -Filter *.md $posts = @() Foreach ($post in $markdownPosts) { $postContent = Get-Content -Path $post.FullName -Raw $frontMatter = Get-PostFrontMatter $postContent $frontMatterObject = $frontMatter | ConvertFrom-Yaml $slug = $frontMatterObject.slug ?? (ConvertTo-Slug "$($frontMatterObject.date)-$($frontMatterObject.title)") $body = $postContent.Replace($frontMatter.Value, "") | ConvertFrom-Markdown $postHtml = $layouts.Home -replace '{{header}}', $layouts.Header ` -replace '{{title}}', $frontMatterObject.title ` -replace '{{nav}}', (Set-Navs) ` -replace '{{content}}', $body.Html ` -replace '{{footer}}', $layouts.Footer $postHtml = Set-Headings $postHtml $postHtml | Out-File -FilePath ./build/$slug.html $posts += @{ title = $frontMatterObject.title slug = $slug excerpt = $frontMatterObject.excerpt date = $frontMatterObject.date author = $frontMatterObject.author body = $body.Html } } Return $posts } Function Set-Archive { $posts = Get-Posts $archive = @() $archive = @" <ul> $($posts | ForEach-Object { "<li><a href='$($_.slug).html'>$($_.title)</a></li>" }) </ul> "@ Return $archive -join "`r`n" } Function Copy-ToBuild { $layouts = Get-Layouts $latestPosts = Get-Posts | ForEach-Object { @" <div> <img src="https://via.placeholder.com/300x200" alt="$($_.title)"> <h2>$($_.title)</h2> <p>$($_.excerpt)</p> <a href="$($_.slug).html">ادامه مطلب</a> </div> "@ } $homeLayout = $layouts.Home -replace '{{header}}', $layouts.Header ` -replace '{{nav}}', (Set-Navs) ` -replace '{{title}}', 'بلاگ من' ` -replace '{{content}}', ('<div>' + $latestPosts + '</div>') ` -replace '{{footer}}', $layouts.Footer $homeLayout | Out-File -FilePath ./build/index.html Copy-Item -Path ./img -Destination ./build -Recurse -Force } Function Set-Navs { $navs = @( @{ title = "صفحه اصلی" url = "/sample" }, @{ title = "درباره ما" url = "/sample/about.html" }, @{ title = "تماس با ما" url = "/sample/contact.html" } ) $navLayout = Get-Content -Path ./_layout/_nav.html -Raw $navLayout -replace '{{nav}}', ($navs | ForEach-Object { "<li><a href=""$($_.url)""text-gray-700 hover:text-gray-800 m-2"">$($_.title)</a></li>" }) } Copy-ToBuild
name: Deploy static content to Pages on: push: branches: - main workflow_dispatch: permissions: contents: read pages: write id-token: write concurrency: group: "pages" cancel-in-progress: false jobs: build-and-deploy: runs-on: ubuntu-latest steps: - name: Checkout Repository uses: actions/checkout@v2 - name: Install powershell-yaml module shell: pwsh run: | Set-PSRepository PSGallery -InstallationPolicy Trusted Install-Module powershell-yaml -ErrorAction Stop - name: Setup Pages uses: actions/configure-pages@v3 - name: Build Static Site shell: pwsh run: | . ./set-posts.ps1 - name: Upload Static Site Artifact uses: actions/upload-pages-artifact@v1 with: path: build - name: Deploy to GitHub Pages id: deployment uses: actions/deploy-pages@v2
- درون دایرکتوری posts، مطلب موردنظر را به همراه Front Matter زیر ایجاد کرده و سپس محتویات مطلب را بعد از آن وارد کنید:
--- title: اولین پست من slug: hello date: 2023-04-26 author: سیروان عفیفی tags: [tag1, tag2, tag3] excerpt: این یک پست تستی است در مورد اینکه چطور میتوانیم از این قالب استفاده کنیم --- content
- تغییرات ایجاد شده را کامیت و سپس پوش کنید. به محض پوش کردن تغییرات، GitHub Actions پروسه بیلد را انجام خواهد داد و بلافاصله میتوانید تغییرات را مشاهده نمائید.
کدهای این مطلب را میتوانید از اینجا دریافت کنید.
- با نصب و اجرای Visual Studio 2013 Express for Web یا Visual Studio 2013 شروع کنید.
- یک پروژه جدید بسازید (از صفحه شروع یا منوی فایل)
- گزینه #Visual C و سپس ASP.NET Web Application را انتخاب کنید. نام پروژه را به "WebFormsIdentity" تغییر داده و OK کنید.
- در دیالوگ جدید ASP.NET گزینه Empty را انتخاب کنید.
دقت کنید که دکمه Change Authentication غیرفعال است و هیچ پشتیبانی ای برای احراز هویت در این قالب پروژه وجود ندارد.
افزودن پکیجهای ASP.NET Identity به پروژه
دقت کنید که نصب کردن این پکیج وابستگیها را نیز بصورت خودکار نصب میکند: Entity Framework و ASP.NET Idenity Core.
افزودن فرمهای وب لازم برای ثبت نام کاربران
در دیالوگ باز شده نام فرم را به Register تغییر داده و تایید کنید.
فایل ایجاد شده جدید را باز کرده و کد Markup آن را با قطعه کد زیر جایگزین کنید.
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Register.aspx.cs" Inherits="WebFormsIdentity.Register" %> <!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml"> <head runat="server"> <title></title> </head> <body style=" <form id="form1" runat="server"> <div> <h4 style="Register a new user</h4> <hr /> <p> <asp:Literal runat="server" ID="StatusMessage" /> </p> <div style="margin-bottom:10px"> <asp:Label runat="server" AssociatedControlID="UserName">User name</asp:Label> <div> <asp:TextBox runat="server" ID="UserName" /> </div> </div> <div style="margin-bottom:10px"> <asp:Label runat="server" AssociatedControlID="Password">Password</asp:Label> <div> <asp:TextBox runat="server" ID="Password" TextMode="Password" /> </div> </div> <div style="margin-bottom:10px"> <asp:Label runat="server" AssociatedControlID="ConfirmPassword">Confirm password</asp:Label> <div> <asp:TextBox runat="server" ID="ConfirmPassword" TextMode="Password" /> </div> </div> <div> <div> <asp:Button runat="server" OnClick="CreateUser_Click" Text="Register" /> </div> </div> </div> </form> </body> </html>
این تنها یک نسخه ساده شده Register.aspx است که از چند فیلد فرم و دکمه ای برای ارسال آنها به سرور استفاده میکند.
فایل کد این فرم را باز کرده و محتویات آن را با قطعه کد زیر جایگزین کنید.
using Microsoft.AspNet.Identity; using Microsoft.AspNet.Identity.EntityFramework; using System; using System.Linq; namespace WebFormsIdentity { public partial class Register : System.Web.UI.Page { protected void CreateUser_Click(object sender, EventArgs e) { // Default UserStore constructor uses the default connection string named: DefaultConnection var userStore = new UserStore<IdentityUser>(); var manager = new UserManager<IdentityUser>(userStore); var user = new IdentityUser() { UserName = UserName.Text }; IdentityResult result = manager.Create(user, Password.Text); if (result.Succeeded) { StatusMessage.Text = string.Format("User {0} was created successfully!", user.UserName); } else { StatusMessage.Text = result.Errors.FirstOrDefault(); } } } }
کد این فرم نیز نسخه ای ساده شده است. فایلی که بصورت خودکار توسط VS برای شما ایجاد میشود متفاوت است.
کلاس IdentityUser پیاده سازی پیش فرض EntityFramework از قرارداد IUser است. قرارداد IUser تعریفات حداقلی یک کاربر در ASP.NET Identity Core را در بر میگیرد.
کلاس UserStore پیاده سازی پیش فرض EF از یک فروشگاه کاربر (user store) است. این کلاس چند قرارداد اساسی ASP.NET Identity Core را پیاده سازی میکند: IUserStore, IUserLoginStore, IUserClaimStore و IUserRoleStore.
کلاس UserManager دسترسی به APIهای مربوط به کاربران را فراهم میکند. این کلاس تمامی تغییرات را بصورت خودکار در UserStore ذخیره میکند.
کلاس IdentityResult نتیجه یک عملیات هویتی را معرفی میکند (identity operations).
پوشه App_Data را به پروژه خود اضافه کنید.
فایل Web.config پروژه را باز کنید و رشته اتصال جدیدی برای دیتابیس اطلاعات کاربران اضافه کنید. این دیتابیس در زمان اجرا (runtime) بصورت خودکار توسط EF ساخته میشود. این رشته اتصال شبیه به رشته اتصالی است که هنگام ایجاد پروژه بصورت خودکار برای شما تنظیم میشود.
<?xml version="1.0" encoding="utf-8"?> <!-- For more information on how to configure your ASP.NET application, please visit http://go.microsoft.com/fwlink/?LinkId=169433 --> <configuration> <configSections> <!-- For more information on Entity Framework configuration, visit http://go.microsoft.com/fwlink/?LinkID=237468 --> <section name="entityFramework" type="System.Data.Entity.Internal.ConfigFile.EntityFrameworkSection, EntityFramework, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" requirePermission="false" /> </configSections> <connectionStrings> <add name="DefaultConnection" connectionString="Data Source=(LocalDb)\v11.0;AttachDbFilename=|DataDirectory|\WebFormsIdentity.mdf;Initial Catalog=WebFormsIdentity;Integrated Security=True" providerName="System.Data.SqlClient" /> </connectionStrings> <system.web> <compilation debug="true" targetFramework="4.5" /> <httpRuntime targetFramework="4.5" /> </system.web> <entityFramework> <defaultConnectionFactory type="System.Data.Entity.Infrastructure.LocalDbConnectionFactory, EntityFramework"> <parameters> <parameter value="v11.0" /> </parameters> </defaultConnectionFactory> <providers> <provider invariantName="System.Data.SqlClient" type="System.Data.Entity.SqlServer.SqlProviderServices, EntityFramework.SqlServer" /> </providers> </entityFramework> </configuration>
همانطور که مشاهده میکنید نام این رشته اتصال DefaultConnection است.
روی فایل Register.aspx کلیک راست کنید و گزینه Set As Start Page را انتخاب کنید. اپلیکیشن خود را با کلیدهای ترکیبی Ctrl + F5 اجرا کنید که تمام پروژه را کامپایل نیز خواهد کرد. یک نام کاربری و کلمه عبور وارد کنید و روی Register کلیک کنید.
ASP.NET Identity از اعتبارسنجی نیز پشتیبانی میکند، مثلا در این مرحله میتوانید از اعتبارسنج هایی که توسط ASP.NET Identity Core عرضه میشوند برای کنترل رفتار فیلدهای نام کاربری و کلمه عبور استفاده کنید. اعتبارسنج پیش فرض کاربران (User) که UserValidator نام دارد خاصیتی با نام AllowOnlyAlphanumericUserNames دارد که مقدار پیش فرضش هم true است. اعتبارسنج پیش فرض کلمه عبور (MinimumLengthValidator) اطمینان حاصل میکند که کلمه عبور حداقل 6 کاراکتر باشد. این اعتبارسنجها بصورت propertyها در کلاس UserManager تعریف شده اند و میتوانید آنها را overwrite کنید و اعتبارسنجی سفارشی خود را پیاده کنید. از آنجا که الگوی دیتابیس سیستم عضویت توسط Entity Framework مدیریت میشود، روی الگوی دیتابیس کنترل کامل دارید، پس از Data Annotations نیز میتوانید استفاده کنید.
تایید دیتابیس LocalDbIdentity که توسط EF ساخته میشود
گره (DefaultConnection (WebFormsIdentity و سپس Tables را باز کنید. روی جدول AspNetUsers کلیک راست کرده و Show Table Data را انتخاب کنید.
پیکربندی اپلیکیشن برای استفاده از احراز هویت OWIN
نصب پکیجهای احراز هویت روی پروژه
به دنبال پکیجی با نام Microsoft.Owin.Host.SystemWeb بگردید و آن را نیز نصب کنید.
پکیج Microsoft.Aspnet.Identity.Owin حاوی یک سری کلاس Owin Extension است و امکان مدیریت و پیکربندی OWIN Authentication در پکیجهای ASP.NET Identity Core را فراهم میکند.
پکیج Microsoft.Owin.Host.SystemWeb حاوی یک سرور OWIN است که اجرای اپلیکیشنهای مبتنی بر OWIN را روی IIS و با استفاده از ASP.NET Request Pipeline ممکن میسازد. برای اطلاعات بیشتر به OWIN Middleware in the IIS integrated pipeline مراجعه کنید.
افزودن کلاسهای پیکربندی Startup و Authentication
فایل Startup.cs را باز کنید و قطعه کد زیر را با محتویات آن جایگزین کنید تا احراز هویت OWIN Cookie Authentication پیکربندی شود.
using Microsoft.AspNet.Identity; using Microsoft.Owin; using Microsoft.Owin.Security.Cookies; using Owin; [assembly: OwinStartup(typeof(WebFormsIdentity.Startup))] namespace WebFormsIdentity { public class Startup { public void Configuration(IAppBuilder app) { // For more information on how to configure your application, visit http://go.microsoft.com/fwlink/?LinkID=316888 app.UseCookieAuthentication(new CookieAuthenticationOptions { AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie, LoginPath = new PathString("/Login") }); } } }
این کلاس حاوی خاصیت OwinAttribute است که کلاس راه انداز OWIN را نشانه گذاری میکند. هر اپلیکیشن OWIN یک کلاس راه انداز (startup) دارد که توسط آن میتوانید کامپوننتهای application pipeline را مشخص کنید. برای اطلاعات بیشتر درباره این مدل، به OWIN Startup Class Detection مراجعه فرمایید.
افزودن فرمهای وب برای ثبت نام و ورود کاربران
using Microsoft.AspNet.Identity; using Microsoft.AspNet.Identity.EntityFramework; using Microsoft.Owin.Security; using System; using System.Linq; using System.Web; namespace WebFormsIdentity { public partial class Register : System.Web.UI.Page { protected void CreateUser_Click(object sender, EventArgs e) { // Default UserStore constructor uses the default connection string named: DefaultConnection var userStore = new UserStore<IdentityUser>(); var manager = new UserManager<IdentityUser>(userStore); var user = new IdentityUser() { UserName = UserName.Text }; IdentityResult result = manager.Create(user, Password.Text); if (result.Succeeded) { var authenticationManager = HttpContext.Current.GetOwinContext().Authentication; var userIdentity = manager.CreateIdentity(user, DefaultAuthenticationTypes.ApplicationCookie); authenticationManager.SignIn(new AuthenticationProperties() { }, userIdentity); Response.Redirect("~/Login.aspx"); } else { StatusMessage.Text = result.Errors.FirstOrDefault(); } } } }
فایل Login.aspx را باز کنید و کد Markup آن را مانند قطعه کد زیر تغییر دهید.
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Login.aspx.cs" Inherits="WebFormsIdentity.Login" %> <!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml"> <head runat="server"> <title></title> </head> <body style="font-family: Arial, Helvetica, sans-serif; font-size: small"> <form id="form1" runat="server"> <div> <h4 style="font-size: medium">Log In</h4> <hr /> <asp:PlaceHolder runat="server" ID="LoginStatus" Visible="false"> <p> <asp:Literal runat="server" ID="StatusText" /> </p> </asp:PlaceHolder> <asp:PlaceHolder runat="server" ID="LoginForm" Visible="false"> <div style="margin-bottom: 10px"> <asp:Label runat="server" AssociatedControlID="UserName">User name</asp:Label> <div> <asp:TextBox runat="server" ID="UserName" /> </div> </div> <div style="margin-bottom: 10px"> <asp:Label runat="server" AssociatedControlID="Password">Password</asp:Label> <div> <asp:TextBox runat="server" ID="Password" TextMode="Password" /> </div> </div> <div style="margin-bottom: 10px"> <div> <asp:Button runat="server" OnClick="SignIn" Text="Log in" /> </div> </div> </asp:PlaceHolder> <asp:PlaceHolder runat="server" ID="LogoutButton" Visible="false"> <div> <div> <asp:Button runat="server" OnClick="SignOut" Text="Log out" /> </div> </div> </asp:PlaceHolder> </div> </form> </body> </html>
محتوای فایل Login.aspx.cs را نیز مانند لیست زیر تغییر دهید.
using Microsoft.AspNet.Identity; using Microsoft.AspNet.Identity.EntityFramework; using Microsoft.Owin.Security; using System; using System.Web; using System.Web.UI.WebControls; namespace WebFormsIdentity { public partial class Login : System.Web.UI.Page { protected void Page_Load(object sender, EventArgs e) { if (!IsPostBack) { if (User.Identity.IsAuthenticated) { StatusText.Text = string.Format("Hello {0}!", User.Identity.GetUserName()); LoginStatus.Visible = true; LogoutButton.Visible = true; } else { LoginForm.Visible = true; } } } protected void SignIn(object sender, EventArgs e) { var userStore = new UserStore<IdentityUser>(); var userManager = new UserManager<IdentityUser>(userStore); var user = userManager.Find(UserName.Text, Password.Text); if (user != null) { var authenticationManager = HttpContext.Current.GetOwinContext().Authentication; var userIdentity = userManager.CreateIdentity(user, DefaultAuthenticationTypes.ApplicationCookie); authenticationManager.SignIn(new AuthenticationProperties() { IsPersistent = false }, userIdentity); Response.Redirect("~/Login.aspx"); } else { StatusText.Text = "Invalid username or password."; LoginStatus.Visible = true; } } protected void SignOut(object sender, EventArgs e) { var authenticationManager = HttpContext.Current.GetOwinContext().Authentication; authenticationManager.SignOut(); Response.Redirect("~/Login.aspx"); } } }
- متد Page_Load حالا وضعیت کاربر جاری را بررسی میکند و بر اساس وضعیت Context.User.Identity.IsAuthenticated تصمیم گیری میکند.
- متد SignIn
- پروژه را با Ctrl + F5 اجرا کنید و کاربر جدیدی بسازید. پس از وارد کردن نام کاربری و کلمه عبور و کلیک کردن دکمه Register باید بصورت خودکار به سایت وارد شوید و نام خود را مشاهده کنید.
- همانطور که مشاهده میکنید در این مرحله حساب کاربری جدید ایجاد شده و به سایت وارد شده اید. روی Log out کلیک کنید تا از سایت خارج شوید. پس از آن باید به صفحه ورود هدایت شوید.
- حالا یک نام کاربری یا کلمه عبور نامعتبر وارد کنید و روی Log in کلیک کنید.
Summary
JavaScript is a language written for websites to run in the client’s browser.
AJAX is a way for JavaScript to request data from a server without refreshing the page or blocking the application.
jQuery is a JavaScript library built to automate and simplify common web tasks like AJAX or animation.
Angular is a hip JavaScript framework which is made for building large, single-page web applications.
Node.js allows JavaScript to be run without a browser, and is commonly used to run web servers.