مقایسهای بین Go و NET.
C# Generics پیشرفته
قالبهایی برای Bootstrap
بررسی 10 مفهوم پایهای در JavaScript
" از عبارت ON برای مشخص کردن محدوده Trigger در سطح SQL Instance (در این صورت ON All SERVER نوشته میشود) و یا در سطح Database (در این حالت ON DATABASE نوشته میشود) استفاده میشود و از عبارت FOR برای مشخص کردن رویداد یا گروه رویدادی که سبب فراخوانی Trigger میشود، استفاده خواهد شد. "
در خصوص مثالی که اشاره کردید، به نظرم میرسد از Trigger برای این منظور استفاده نمیشود (در حوزهی بکاپ و ریستور)، شاید اگر قصدتان به منظور ثبت log و ... بایست از Auditing استفاده کنید. به این منظور در Auditing با توجه به جدول زیر میتوان اقدام به ثبت موارد نمود:
Action group name | Event Class | Description |
BACKUP_RESTORE_GROUP | Audit Backup/Restore | یک دستور Backup یا Restore صادر شود |
بررسی SQL Server Audit
بازبینی (Auditing) شامل پیگیری و ثبت رویدادهایی است که در سطح SQL Instance و یا Databaseهای روی یک سیستم اتفاق میافتد. چندین سطح برای Auditing در SQL Server وجود دارد که به صلاحدید و نیازمندیهای نصب شما وابسته است. شما میتوانید گروه اقدامات بازبینی سرویس دهنده (server audit action groups) را به ازای هر SQL Instance و گروه اقدامات بازبینی بانک اطلاعاتی (database audit action groups) را به ازای هر بانک اطلاعاتی ثبت کنید. رویداد Audit هر زمان عملی که مورد رسیدگی قرار گرفته اتفاق افتد، رخ میدهد.
تا پیش از SQL SERVER 2008، شما باید از خصیصههای متعددی برای انجام یک مجموعه کامل بازبینی (Auditing) برای نمونه DDL Trigger، DML Trigger و SQL Trace، بر روی یک SQL Instance استفاده میکردید.
SQL SERVER 2008، همه قابلیتهای Auditing را روی یک audit specification ترکیب میکند. Audit Specification با تعریف یک شی بازبینی (audit object) در سطح سرویس دهنده برای ثبت (logging) یک دنباله بازبینی (audit trial) آغاز میشود. توجه شود که بایست یک شیء بازبینی ایجاد کنید پیش از اینکه یک Server Audit Specification و یا Database Audit Specification ایجاد کنید.
Server Audit Specification، گروه اقدامات در سطح سرویس دهنده را جمع آوری میکند که با رویدادهای وسیعی فعال میشوند، این گروه اقدامات تحت عنوان Server-Level Audit Action Groups تشریح شده اند. شما میتوانید یک Server Audit Specification را به ازای هر Audit ایجاد کنید چرا که هر دو در محدوده یک SQL Instance ایجاد میشوند.
Database Audit Specification، گروه اقدامات در سطح بانک اطلاعاتی را جمع آوری میکند که با رویدادهای وسیعی فعال میشود. این گروه اقدامات تحت عنوانهای Database-Level Audit Action Groups و Database-Level Audit Actions تشریح شده اند.می توانید یک Database Audit Specification را به ازای هر Audit در بانک اطلاعاتی SQL Server ایجاد کنید.
همچنین میتوانید هر گروه اقدامات بازبینی(audit action groups) یا رویدادهای بازبینی(audit events) را به یک Database Audit Specification اضافه کنید. گروه اقدامات بازبینی، گروه اقدامات از پیش تعریف شده ای هستند و رویدادهای بازبینی اقدامات تجزیه ناپذیری هستند که توسط موتور بانک اطلاعاتی مورد رسیدگی قرار میگیرند، هر دو در محدوده بانک اطلاعاتی (Database) هستند. این اقدامات برای Audit فرستاده میشوند تا در Target (که میتواند یک فایل، Windows Security Log و یا Windows Application Log باشد) ذخیره شوند. برای نوشتن در Windows Security Log لازم است که Service Account سرویس دهنده شما به Policy، Generate security audits اضافه شده باشد، به صورت پیش فرض Local System، Local Service و Network Service بخشی از این Policy میباشند. پس از اینکه Audit را ایجاد و فعال کردید، Target ورودیها را دریافت خواهد کرد.
Server-Level Audit Action Groups (گروه اقدامات بازبینی در سطح سرویس دهنده)
این گروه اقدامات به گروه رویداد Security Audit شبیه هستند. به طور خلاصه این گروه اقدامات، اقداماتی را که در یک SQL Instance شامل میشوند، در بر میگیرد. برای مثال اگر گروه اقدام مناسب با Server Audit Specification اضافه شده باشد هر شیء در هر Schema که مورد دستیابی قرار میگیرد، ثبت میشود. اقدامات در سطح سرویس دهنده به شما اجازه نمیدهد که جزئیات اقدامات در سطح بانک اطلاعاتی را فیلتر کنید. یک بازبینی در سطح بانک اطلاعاتی برای انجام به جزئیات دقیق فیلتر کردن نیاز دارد، برای مثال اجرای دستور Select روی جدول Customers برای login هایی که در گروه Employee هستند.
Database-Level Audit Action Groups (گروه اقدامات بازبینی در سطح بانک اطلاعاتی)
این گروه اعمال به کلاسهای رویداد Security Audit شبیه هستند.
Database-Level Audit Actions
اقدامات در سطح بانک اطلاعاتی، اقدامات بازبینی خاصی را به طور مستقیم روی Database، Schema و اشیاء Schema (از قبیل جداول، View ها، رویههای ذخیره شده، توابع و ... ) فراهم میکند. این اقدامات برای فیلدها (Columns) صدق نمیکنند.
Audit-Level Audit Action Groups
شما میتوانید اقداماتی را که در فرآیند Auditing هستند، بازبینی کنید که میتواند در محدوده سرویس دهنده یا بانک اطلاعاتی باشد. در محدوده بانک اطلاعاتی تنها برای database audit specification رخ میدهد.
جهت بررسی بیشتر به این لینک مراجعه شود.
کدهای سمت سرور دریافت فایل PDF
using Microsoft.AspNetCore.Mvc; namespace BlazorWasmShowBinaryFiles.Server.Controllers { [ApiController] [Route("api/[controller]")] public class ReportsController : ControllerBase { [HttpGet("[action]")] public IActionResult GetPdfReport() { //TODO: create the `sample.pdf` report file on the server return File(virtualPath: "~/app_data/sample.pdf", contentType: "application/pdf", fileDownloadName: "sample.pdf"); } } }
ساخت URL برای دسترسی به اطلاعات باینری
تمام مرورگرهای جدید از ایجاد URL برای اشیاء Blob دریافتی از سمت سرور، توسط متد توکار URL.createObjectURL پشتیبانی میکنند. این متد، شیء URL را از شیء window جاری دریافت میکند و سپس اطلاعات باینری را دریافت کرده و آدرسی را جهت دسترسی موقت به آن تولید میکند. حاصل آن، یک URL ویژهاست مانند blob:https://localhost:5001/03edcadf-89fd-48b9-8a4a-e9acf09afd67 که گشودن آن در مرورگر، یا سبب نمایش آن تصویر و یا دریافت مستقیم فایل خواهد شد.
در برنامههای Blazor نیاز است اینکار را توسط JS Interop آن انجام داد؛ از این جهت که API تولید یک Blob URL، صرفا توسط کدهای جاوا اسکریپتی قابل دسترسی است. به همین جهت فایل جدید Client\wwwroot\site.js را با محتوای زیر ایجاد کرده و همچنین مدخل آنرا در به انتهای فایل Client\wwwroot\index.html، پیش از بسته شدن تگ body، اضافه میکنیم:
window.JsBinaryFilesUtils = { createBlobUrl: function (byteArray, contentType) { // The byte array in .NET is encoded to base64 string when it passes to JavaScript. const numArray = atob(byteArray) .split("") .map((c) => c.charCodeAt(0)); const uint8Array = new Uint8Array(numArray); const blob = new Blob([uint8Array], { type: contentType }); return URL.createObjectURL(blob); }, downloadFromUrl: function (fileName, url) { const anchor = document.createElement("a"); anchor.style.display = "none"; anchor.href = url; anchor.download = fileName; document.body.appendChild(anchor); anchor.click(); document.body.removeChild(anchor); }, downloadBlazorByteArray: function (fileName, byteArray, contentType) { const blobUrl = this.createBlobUrl(byteArray, contentType); this.downloadFromUrl(fileName, blobUrl); URL.revokeObjectURL(blobUrl); }, printFromUrl: function (url) { const iframe = document.createElement("iframe"); iframe.style.display = "none"; iframe.src = url; document.body.appendChild(iframe); if (iframe.contentWindow) { iframe.contentWindow.print(); } }, printBlazorByteArray: function (byteArray, contentType) { const blobUrl = this.createBlobUrl(byteArray, contentType); this.printFromUrl(blobUrl); URL.revokeObjectURL(blobUrl); }, showUrlInNewTab: function (url) { window.open(url); }, showBlazorByteArrayInNewTab: function (byteArray, contentType) { const blobUrl = this.createBlobUrl(byteArray, contentType); this.showUrlInNewTab(blobUrl); URL.revokeObjectURL(blobUrl); }, };
- زمانیکه در برنامههای Blazor با استفاده از متد ()HttpClient.GetByteArrayAsync آرایهای از بایتهای یک فایل باینری را دریافت میکنیم، ارسال آن به کدهای جاوااسکریپتی به صورت یک رشتهی base64 شده صورت میگیرد (JS Interop اینکار را به صورت خودکار انجام میدهد). به همین جهت در متد createBlobUrl روش تبدیل این رشتهی base64 دریافتی را به آرایهای از بایتها، سپس به یک Blob و در آخر به یک Blob URL، مشاهده میکنید. این Blob Url اکنون آدرس موقتی دسترسی به آرایهای از بایتهای دریافتی توسط مرورگر است. به همین جهت میتوان از آن به عنوان src بسیاری از اشیاء HTML استفاده کرد.
- متد downloadFromUrl، کار دریافت یک Url و سپس دانلود خودکار آنرا انجام میدهد. اگر به یک anchor استاندارد HTML، ویژگی download را نیز اضافه کنیم، با کلیک بر روی آن، بجای گشوده شدن این Url، مرورگر آنرا دریافت خواهد کرد. متد downloadFromUrl کار ساخت لینک و تنظیم ویژگیهای آن و سپس کلیک بر روی آنرا به صورت خودکار انجام میدهد. از متد downloadFromUrl زمانی استفاده کنید که منبع مدنظر، محافظت شده نباشد و Url آن به سادگی در مرورگر قابل گشودن باشد.
- متد downloadBlazorByteArray همان کار متد downloadFromUrl را انجام میدهد؛ با این تفاوت که Url مورد نیاز توسط متد downloadFromUrl را از طریق یک Blob Url تامین میکند.
- متد printFromUrl که جهت دسترسی به منابع محافظت نشده طراحی شدهاست، Url یک منبع را دریافت کرده، آنرا به یک iframe اضافه میکند و سپس متد print را بر روی این iframe به صورت خودکار فراخوانی خواهد کرد تا سبب ظاهر شدن صفحهی پیشنمایش چاپ شود.
- printBlazorByteArray همان کار متد printFromUrl را انجام میدهد؛ با این تفاوت که Url مورد نیاز توسط متد printFromUrl را از طریق یک Blob Url تامین میکند.
تهیهی متدهایی الحاقی جهت کار سادهتر با JsBinaryFilesUtils
پس از تهیهی JsBinaryFilesUtils فوق، میتوان با استفاده از کلاس زیر که به همراه متدهایی الحاقی جهت دسترسی به امکانات آن است، کار با متدهای دریافت، نمایش و چاپ فایلهای باینری را سادهتر کرد و از تکرار کدها جلوگیری نمود:
using System.Threading.Tasks; using Microsoft.JSInterop; namespace BlazorWasmShowBinaryFiles.Client.Utils { public static class JsBinaryFilesUtils { public static ValueTask<string> CreateBlobUrlAsync( this IJSRuntime JSRuntime, byte[] byteArray, string contentType) { return JSRuntime.InvokeAsync<string>("JsBinaryFilesUtils.createBlobUrl", byteArray, contentType); } public static ValueTask DownloadFromUrlAsync(this IJSRuntime JSRuntime, string fileName, string url) { return JSRuntime.InvokeVoidAsync("JsBinaryFilesUtils.downloadFromUrl", fileName, url); } public static ValueTask DownloadBlazorByteArrayAsync( this IJSRuntime JSRuntime, string fileName, byte[] byteArray, string contentType) { return JSRuntime.InvokeVoidAsync("JsBinaryFilesUtils.downloadBlazorByteArray", fileName, byteArray, contentType); } public static ValueTask PrintFromUrlAsync(this IJSRuntime JSRuntime, string url) { return JSRuntime.InvokeVoidAsync("JsBinaryFilesUtils.printFromUrl", url); } public static ValueTask PrintBlazorByteArrayAsync( this IJSRuntime JSRuntime, byte[] byteArray, string contentType) { return JSRuntime.InvokeVoidAsync("JsBinaryFilesUtils.printBlazorByteArray", byteArray, contentType); } public static ValueTask ShowUrlInNewTabAsync(this IJSRuntime JSRuntime, string url) { return JSRuntime.InvokeVoidAsync("JsBinaryFilesUtils.showUrlInNewTab", url); } public static ValueTask ShowBlazorByteArrayInNewTabAsync( this IJSRuntime JSRuntime, byte[] byteArray, string contentType) { return JSRuntime.InvokeVoidAsync("JsBinaryFilesUtils.showBlazorByteArrayInNewTab", byteArray, contentType); } } }
اصلاح Content Security Policy سمت سرور جهت ارائهی محتوای blob
پس از دریافت فایل PDF به صورت یک blob، با استفاده از متد URL.createObjectURL میتوان آدرس موقت محلی را برای دسترسی به آن تولید کرد و یک چنین آدرسهایی به صورت blob:http تولید میشوند. در این حالت در Content Security Policy سمت سرور، نیاز است امکان دسترسی به تصاویر و همچنین اشیاء از نوع blob را نیز آزاد معرفی کنید:
img-src 'self' data: blob: default-src 'self' blob: object-src 'self' blob:
نمایش فایل PDF دریافتی از سرور، به همراه دکمههای دریافت، چاپ و نمایش آن در صفحهی جاری
در ادامه کدهای کامل مرتبط با تصویری را که در ابتدای بحث مشاهده کردید، ملاحظه میکنید:
@page "/" @using BlazorWasmShowBinaryFiles.Client.Utils @inject IJSRuntime JSRuntime @inject HttpClient HttpClient <h1>Display PDF Files</h1> <button class="btn btn-info" @onclick="handlePrintPdf">Print PDF</button> <button class="btn btn-primary ml-2" @onclick="handleShowPdf">Show PDF</button> <button class="btn btn-success ml-2" @onclick="handleDownloadPdf">Download PDF</button> @if(!string.IsNullOrWhiteSpace(PdfBlobUrl)) { <section class="card mb-5 mt-3"> <div class="card-header"> <h4>using iframe</h4> </div> <div class="card-body"> <iframe title="PDF Report" width="100%" height="600" src="@PdfBlobUrl" type="@PdfContentType"></iframe> </div> </section> <section class="card mb-5"> <div class="card-header"> <h4>using object</h4> </div> <div class="card-body"> <object data="@PdfBlobUrl" aria-label="PDF Report" type="@PdfContentType" width="100%" height="100%"></object> </div> </section> <section class="card mb-5"> <div class="card-header"> <h4>using embed</h4> </div> <div class="card-body"> <embed aria-label="PDF Report" src="@PdfBlobUrl" type="@PdfContentType" width="100%" height="100%"> </div> </section> } @code { private const string ReportUrl = "/api/Reports/GetPdfReport"; private const string PdfContentType = "application/pdf"; private string PdfBlobUrl; private async Task handlePrintPdf() { // Note: Using the `HttpClient` is useful for accessing the protected API's by JWT's (non cookie-based authorization). // Otherwise just use the `PrintFromUrlAsync` method. var byteArray = await HttpClient.GetByteArrayAsync(ReportUrl); await JSRuntime.PrintBlazorByteArrayAsync(byteArray, PdfContentType); } private async Task handleDownloadPdf() { // Note: Using the `HttpClient` is useful for accessing the protected API's by JWT's (non cookie-based authorization). // Otherwise just use the `DownloadFromUrlAsync` method. var byteArray = await HttpClient.GetByteArrayAsync(ReportUrl); await JSRuntime.DownloadBlazorByteArrayAsync("report.pdf", byteArray, PdfContentType); } private async Task handleShowPdf() { // Note: Using the `HttpClient` is useful for accessing the protected API's by JWT's (non cookie-based authorization). // Otherwise just use the `ReportUrl` as the `src` of the `iframe` directly. var byteArray = await HttpClient.GetByteArrayAsync(ReportUrl); PdfBlobUrl = await JSRuntime.CreateBlobUrlAsync(byteArray, PdfContentType); } // Tips: // 1- How do I enable/disable the built-in pdf viewer of FireFox // https://support.mozilla.org/en-US/kb/disable-built-pdf-viewer-and-use-another-viewer // 2- How to configure browsers to use the Adobe PDF plug-in to open PDF files // https://helpx.adobe.com/acrobat/kb/pdf-browser-plugin-configuration.html // https://helpx.adobe.com/acrobat/using/display-pdf-in-browser.html // 3- Microsoft Edge is gaining new PDF reader features within the Windows 10 Fall Creator’s Update (version 1709). }
- پس از تهیهی JsBinaryFilesUtils و متدهای الحاقی متناظر با آن، اکنون تنها کافی است با استفاده از متد ()HttpClient.GetByteArrayAsync، فایل PDF ارائه شدهی توسط یک اکشن متد را به صورت آرایهای از بایتها دریافت و سپس به متدهای چاپ (PrintBlazorByteArrayAsync) و دریافت (DownloadBlazorByteArrayAsync) آن ارسال کنیم.
- در مورد نمایش آرایهای از بایتهای دریافتی، وضعیت کمی متفاوت است. ابتدا باید توسط متد CreateBlobUrlAsync، آدرس موقتی این آرایه را در مرورگر تولید کرد و سپس این آدرس را برای مثال به src یک iframe انتساب دهیم تا PDF را با استفاده از امکانات توکار مرورگر، نمایش دهد.
کدهای کامل این مطلب را از اینجا میتوانید دریافت کنید: BlazorWasmShowBinaryFiles.zip
یکی دیگر از ماژولهایی که امکان اتصال Node.js را به SQL Server ممکن میکند، Edge.js است. Edge.js یک ماژول Node.js است که امکان اجرای کدهای دات نت را در همان پروسه توسط Node.js فراهم میکند. این مسئله، توسعه دهندگان Node.js را قادر میسازد تا از فناوریهایی که به صورت سنتی استفادهی از آنها سخت یا غیر ممکن بوده است را به راحتی استفاده کنند. برای نمونه:
- SQL Server
- Active Directory
- Nuget packages
- استفاده از سخت افزار کامپیوتر (مانند وب کم، میکروفن و چاپگر)
نصب Node.js
اگر Node.js را بر روی سیستم خود نصب ندارید، میتوانید از اینجا آن را دانلود کنید. بعد از نصب برای اطمینان از کارکرد آن، command prompt را باز کرده و دستور زیر را تایپ کنید:
node -v
ایجاد پوشه پروژه
سپس پوشهای را برای پروژه Node.js خود ایجاد کنید. مثلا با استفاده از command prompt و دستور زیر:
md \projects\node-edge-test1 cd \projects\node-edge-test1
نصب Edge.js
Node با استفاده از package manager خود دانلود و نصب ماژولها را خیلی آسان کرده است. برای نصب، در command prompt عبارت زیر را تایپ کنید:
npm install edge npm install edge-sql
Hello World
ایجاد یک فایل متنی با نام server.js و نوشتن کد زیر در آن:var edge = require('edge'); // The text in edge.func() is C# code var helloWorld = edge.func('async (input) => { return input.ToString(); }'); helloWorld('Hello World!', function (error, result) { if (error) throw error; console.log(result); });
node server.js
ایجاد پایگاه داده تست
در مثالهای بعدی، نیاز به یک پایگاه داده داریم تا queryها را اجرا کنیم. در صورتی که SQL Server بر روی سیستم شما نصب نیست، میتوانید نسخهی رایگان آن را از اینجا دانلود و نصب کنید. همچنین SQL Management Studio Express را نیز نصب کنید.
- در SQL Management Studio، یک پایگاه داده را با نام node-test با تنظیمات پیش فرض ایجاد کنید.
- بر روی پایگاه داده node-test راست کلیک کرده و New Query را انتخاب کنید.
- اسکریپت زیر را copy کرده و در آنجا paste کنید، سپس بر روی Execute کلیک کنید.
IF EXISTS(SELECT 1 FROM sys.tables WHERE object_id = OBJECT_ID('SampleUsers')) BEGIN; DROP TABLE SampleUsers; END; GO CREATE TABLE SampleUsers ( Id INTEGER NOT NULL IDENTITY(1, 1), FirstName VARCHAR(255) NOT NULL, LastName VARCHAR(255) NOT NULL, Email VARCHAR(255) NOT NULL, CreateDate DATETIME NOT NULL DEFAULT(getdate()), PRIMARY KEY (Id) ); GO INSERT INTO SampleUsers(FirstName,LastName,Email,CreateDate) VALUES('Orla','Sweeney','nunc@convallisincursus.ca','Apr 13, 2014'); INSERT INTO SampleUsers(FirstName,LastName,Email,CreateDate) VALUES('Zia','Pickett','porttitor.tellus.non@Duis.com','Aug 31, 2014'); INSERT INTO SampleUsers(FirstName,LastName,Email,CreateDate) VALUES('Justina','Ayala','neque.tellus.imperdiet@temporestac.com','Jul 28, 2014'); INSERT INTO SampleUsers(FirstName,LastName,Email,CreateDate) VALUES('Levi','Parrish','adipiscing.elit@velarcueu.com','Jun 21, 2014'); INSERT INTO SampleUsers(FirstName,LastName,Email,CreateDate) VALUES('Pearl','Warren','In@dignissimpharetra.org','Mar 3, 2014');
تنظیمات ConnectionString
قبل از استفاده از Edge.js با SQL Server، باید متغیر محیطی (environment variable) با نام EDGE_SQL_CONNECTION_STRING را تعریف کنید.
set EDGE_SQL_CONNECTION_STRING=Data Source=localhost;Initial Catalog=node-test;Integrated Security=True
SETX EDGE_SQL_CONNECTION_STRING "Data Source=localhost;Initial Catalog=node-test;Integrated Security=True"
روش اول: اجرای مستقیم SQL Server Query در Edge.js
فایلی با نام server-sql-query.js را ایجاد کرده و کد زیر را در آن وارد کنید:
var http = require('http'); var edge = require('edge'); var port = process.env.PORT || 8080; var getTopUsers = edge.func('sql', function () {/* SELECT TOP 3 * FROM SampleUsers ORDER BY CreateDate DESC */}); function logError(err, res) { res.writeHead(200, { 'Content-Type': 'text/plain' }); res.write("Error: " + err); res.end(""); } http.createServer(function (req, res) { res.writeHead(200, { 'Content-Type': 'text/html' }); getTopUsers(null, function (error, result) { if (error) { logError(error, res); return; } if (result) { res.write("<ul>"); result.forEach(function(user) { res.write("<li>" + user.FirstName + " " + user.LastName + ": " + user.Email + "</li>"); }); res.end("</ul>"); } else { } }); }).listen(port); console.log("Node server listening on port " + port);
node server-sql-query.js
روش دوم: اجرای کد دات نت برای SQL Server Query
Edge.js تنها از دستورات Update، Insert، Select و Delete پشتیبانی میکند. در حال حاضر از store procedures و مجموعهای از کد SQL پشتیبانی نمیکند. بنابراین، اگر چیزی بیشتر از عملیات CRUD میخواهید انجام دهید، باید از دات نت برای این کار استفاده کنید.یادتان باشد، همیشه async
مدل اجرایی Node.js به صورت یک حلقهی رویداد تک نخی است. بنابراین این بسیار مهم است که کد دات نت شما به صورت async باشد. در غیر اینصورت یک فراخوانی به دات نت سبب مسدود شدن و ایجاد خرابی در Node.js میشود.
ایجاد یک Class Library
اولین قدم، ایجاد یک پروژه Class Library در Visual Studio که خروجی آن یک فایل DLL است و استفاده از آن در Edge.js است. پروژه Class Library با عنوان EdgeSampleLibrary ایجاد کرده و فایل کلاسی با نام Sample1 را به آن اضافه کنید و سپس کد زیر را در آن وارد کنید:
using System; using System.Collections.Generic; using System.Data; using System.Data.SqlClient; using System.Threading.Tasks; namespace EdgeSampleLibrary { public class Sample1 { public async Task<object> Invoke(object input) { // Edge marshalls data to .NET using an IDictionary<string, object> var payload = (IDictionary<string, object>) input; var pageNumber = (int) payload["pageNumber"]; var pageSize = (int) payload["pageSize"]; return await QueryUsers(pageNumber, pageSize); } public async Task<List<SampleUser>> QueryUsers(int pageNumber, int pageSize) { // Use the same connection string env variable var connectionString = Environment.GetEnvironmentVariable("EDGE_SQL_CONNECTION_STRING"); if (connectionString == null) throw new ArgumentException("You must set the EDGE_SQL_CONNECTION_STRING environment variable."); // Paging the result set using a common table expression (CTE). // You may rather do this in a stored procedure or use an // ORM that supports async. var sql = @" DECLARE @RowStart int, @RowEnd int; SET @RowStart = (@PageNumber - 1) * @PageSize + 1; SET @RowEnd = @PageNumber * @PageSize; WITH Paging AS ( SELECT ROW_NUMBER() OVER (ORDER BY CreateDate DESC) AS RowNum, Id, FirstName, LastName, Email, CreateDate FROM SampleUsers ) SELECT Id, FirstName, LastName, Email, CreateDate FROM Paging WHERE RowNum BETWEEN @RowStart AND @RowEnd ORDER BY RowNum; "; var users = new List<SampleUser>(); using (var cnx = new SqlConnection(connectionString)) { using (var cmd = new SqlCommand(sql, cnx)) { await cnx.OpenAsync(); cmd.Parameters.Add(new SqlParameter("@PageNumber", SqlDbType.Int) { Value = pageNumber }); cmd.Parameters.Add(new SqlParameter("@PageSize", SqlDbType.Int) { Value = pageSize }); using (var reader = await cmd.ExecuteReaderAsync(CommandBehavior.CloseConnection)) { while (await reader.ReadAsync()) { var user = new SampleUser { Id = reader.GetInt32(0), FirstName = reader.GetString(1), LastName = reader.GetString(2), Email = reader.GetString(3), CreateDate = reader.GetDateTime(4) }; users.Add(user); } } } } return users; } } public class SampleUser { public int Id { get; set; } public string FirstName { get; set; } public string LastName { get; set; } public string Email { get; set; } public DateTime CreateDate { get; set; } } }
[project]/bin/Debug/EdgeSampleLibrary.dll
var http = require('http'); var edge = require('edge'); var port = process.env.PORT || 8080; // Set up the assembly to call from Node.js var querySample = edge.func({ assemblyFile: 'EdgeSampleLibrary.dll', typeName: 'EdgeSampleLibrary.Sample1', methodName: 'Invoke' }); function logError(err, res) { res.writeHead(200, { 'Content-Type': 'text/plain' }); res.write("Got error: " + err); res.end(""); } http.createServer(function (req, res) { res.writeHead(200, { 'Content-Type': 'text/html' }); // This is the data we will pass to .NET var data = { pageNumber: 1, pageSize: 3 }; // Invoke the .NET function querySample(data, function (error, result) { if (error) { logError(error, res); return; } if (result) { res.write("<ul>"); result.forEach(function(user) { res.write("<li>" + user.FirstName + " " + user.LastName + ": " + user.Email + "</li>"); }); res.end("</ul>"); } else { res.end("No results"); } }); }).listen(port); console.log("Node server listening on port " + port);
node server-dotnet-query.js
نکته: برای ایجاد pageNumber و pageSize داینامیک با استفاده از ارسال مقادیر توسط QueryString، میتوانید از ماژول connect استفاده کنید.
WAS یا Windows Process Activation Service
پیکربندی مدیریتی در WAS
- Global configuration information
- Protocol configuration information for both HTTP and non-HTTP protocols
- Application pool configuration, such as the process account information
- Site configuration, such as bindings and applications
- Application configuration, such as the enabled protocols and the application pools to which the applications belong
مدیریت پروسهها Process Managment
نکته: از آنجایی که WAS هم پروسههای http و هم non-http را مدیریت میکند، پس میتوانید از یک applicatio pool برای چندین protocol استفاده کنید. به عنوان مثال شما یکی سرویس XML دارید که میتوانید از آن برای سرویس دهی به پروتوکلهای Http و net.tcp بهره بگیرید.
ماژولها در IIS
ماژولهای کد ماشین یا native
جدول ماژولهای HTTP
نام ماژول | توضیحات | نام فایل منبع |
CustomErrorModule | موقعی که هنگام response، کد خطایی تولید میگردد، پیام خطا را پیکربندی و سپس ارسال میکند. | Inetsrv\Custerr.dll |
HttpRedirectionModule | تنظمیات redirection برای درخواستهای http را در دسترس قرار میدهد. | Inetsrv\Redirect.dll |
ProtocolSupportModule | انجام عملیات مربوط به پروتوکلها بر عهده این ماژول است؛ مثل تنظیم کردن قسمت هدر برای response. | Inetsrv\Protsup.dll |
RequestFilteringModule | این ماژول از IIS 7.5 به بعد اضافه شد. درخواستها را فیلتر میکند تا پروتوکل و رفتار محتوا را کنترل کند. | Inetsrv\modrqflt.dll |
WebDAVModule | این ماژول از IIS 7.5 به بعد اضافه شد. امنیت بیشتر در هنگام انتشار محتوا روی HTTP SSL | Inetsrv\WebDAV.dll |
ماژولهای امنیتی
نام ماژول | توضیحات | نام فایل منبع |
AnonymousAuthenticationModule | موقعی که هیچ کدام از عملیات authentication با موفقیت روبرو نشود، عملیات Anonymous authentication انجام میشود. | Inetsrv\Authanon.dll |
BasicAuthenticationModule | عمل ساده و اساسی authentication را انجام میدهد. | Inetsrv\Authbas.dll |
CertificateMappingAuthenticationModule | انجام عمل Certificate Mapping authentication در Active Directory | Inetsrv\Authcert.dll |
DigestAuthenticationModule | Digest authentication | Inetsrv\Authmd5.dll |
IISCertificateMappingAuthenticationModule | همان Certificate Mapping authentication ولی اینبار با IIS Certificate . | Inetsrv\Authmap.dll |
RequestFilteringModule | عملیات اسکن URL از قبیل نام صفحات و دایرکتوریها ، توع verb و یا کاراکترهای مشکوک و خطرآفرین | Inetsrv\Modrqflt.dll |
UrlAuthorizationModule | عمل URL authorization | Inetsrv\Urlauthz.dll |
WindowsAuthenticationModule | عمل NTLM integrated authentication | Inetsrv\Authsspi.dll |
IpRestrictionModule | محدود کردن IPهای نسخه 4 لیست شده در IP Security در قسمت پیکربندی | Inetsrv\iprestr.dll |
ماژولهای محتوا
نام ماژول | توضیحات | نام فایل منبع |
CgiModule | ایجاد پردازشهای (Common Gateway Interface (CGI به منظور ایجاد خروجی response | Inetsrv\Cgi.dll |
DefaultDocumentModule | تلاش برای ساخت یک سند پیش فرض برای درخواست هایی که دایرکتوری والد ارسال میشود | Inetsrv\Defdoc.dll |
DirectoryListingModule | لیست کردن محتوای یک دایرکتوری | Inetsrv\dirlist.dll |
IsapiModule | میزبانی فایل های ISAPI | Inetsrv\Isapi.dll |
IsapiFilterModule | پشتیبانی از فیلتر های ISAPI | Inetsrv\Filter.dll |
ServerSideIncludeModule | پردازش کدهای include شده سمت سرور | Inetsrv\Iis_ssi.dll |
StaticFileModule | ارائه فایلهای ایستا | Inetsrv\Static.dll |
FastCgiModule | پشتبانی از CGI | Inetsrv\iisfcgi.dll |
ماژولهای فشرده سازی
DynamicCompressionModule | فشرده سازی پاسخ response با gzip | Inetsrv\Compdyn.dll |
StaticCompressionModule | فشرده سازی محتوای ایستا | Inetsrv\Compstat.dll |
ماژولهای کش کردن
FileCacheModule | تهیه کش در مد کاربری برای فایلها. | Inetsrv\Cachfile.dll |
HTTPCacheModule | تهیه کش مد کاربری و مد کرنل برای http.sys | Inetsrv\Cachhttp.dll |
TokenCacheModule | تهیه کش مد کاربری بر اساس جفت نام کاربری و یک token که توسط Windows user principals تولید شده است. | Inetsrv\Cachtokn.dll |
UriCacheModule | تهیه یک کش مد کاربری از اطلاعات URL | Inetsrv\Cachuri.dll |
ماژولهای عیب یابی و لاگ کردن
CustomLoggingModule | بارگزاری ماژولهای خصوصی سازی شده جهت لاگ کردن | Inetsrv\Logcust.dll |
FailedRequestsTracingModule | برای ردیابی درخواستهای ناموفق | Inetsrv\Iisfreb.dll |
HttpLoggingModule | دریافت اطلاعات و پردازش وضعیت http.sys برای لاگ کردن | Inetsrv\Loghttp.dll |
RequestMonitorModule | ردیابی درخواست هایی که در حال حاضر در پروسههای کارگر در حال اجرا هستند و گزارش اطلاعاتی در مورد وضعیت اجرا و کنترل رابط برنامه نویسی کاربردی. | Inetsrv\Iisreqs.dll |
TracingModule | گزارش رخدادهای Microsoft Event Tracing for Windows یا به اختصار ETW | Inetsrv\Iisetw.dll |
ماژولهای مدیریتی و نظارتی بر کل ماژولها
ManagedEngine | مدیرتی بر ماژولهای غیر native که در پایین قرار دارند. | Microsoft.NET\Framework\v2.0.50727\webengine.dll |
ConfigurationValidationModule | اعتبارسنجی خطاها، مثل موقعی که برنامه در حالت integrated اجرا شده و ماژولها یا هندلرها در system.web تعریف شدهاند. | Inetsrv\validcfg.dll |
ماژول | توضیحات | منبع |
AnonymousIdentification | مدیریت منابع تعیین هویت برای کاربران ناشناس مانند asp.net profile | System.Web.Security.AnonymousIdentificationModule |
DefaultAuthentication | اطمینان از وجود شی Authentication در context مربوطه | System.Web.Security.DefaultAuthenticationModule |
FileAuthorization | تایید هویت کاربر برای دسترسی به فایل درخواست | System.Web.Security.FileAuthorizationModule |
FormsAuthentication | با این قسمت که باید کاملا آشنا باشید؛ برای تایید هویت کاربر | System.Web.Security.FormsAuthenticationModule |
OutputCache | مدیریت کش | System.Web.Caching.OutputCacheModule |
Profile | مدیریت پروفایل کاربران که تنظیماتش را در یک منبع دادهای چون دیتابیس ذخیره و بازیابی میکند. | System.Web.Profile.ProfileModule |
RoleManager | مدیریت نقش و سمت کاربران | System.Web.Security.RoleManagerModule |
Session | مدیریت session ها | System.Web.SessionState.SessionStateModule |
UrlAuthorization | آیا کاربر جاری حق دسترسی به URL درخواست را دارد؟ | System.Web.Security.UrlAuthorizationModule |
UrlMappingsModule | تبدیل یک Url واقعی به یک Url کاربرپسند | System.Web.UrlMappingsModule |
WindowsAuthentication | شناسایی و تایید و هویت یک کاربر بر اساس لاگین او به ویندوز | System.Web.Security.WindowsAuthenticationModule |
آسیب پذیری SQL Injection یا به اختصار SQLi
تزریق SQL، یکی از قدیمی ترین، شایعترین و مخربترین آسیب پذیریها، برای برنامهها میباشد و در صورت برقراری شرایط مناسب جهت حمله و با اعمال نفوذ، از طریق تزریق SQL ، مهاجم میتواند با دور زدن فرآیندهای اعتبارسنجی و احراز هویت یک برنامه، به تمامی محتوای پایگاه دادهی آن و گاها کنترل سرور، دسترسی پیدا کند. این حمله برای افزودن، ویرایش و حذف رکوردهای یک پایگاه داده مبتنی بر SQL انجام میشود.
عملکرد SQL Injection
برای اجرای SQLهای مخرب در برنامههایی که از پایگاههای دادهی مبتنی بر SQL مانند (SQL Server ،MySQL ،PostgreSQL ،Oracle و ...) استفاده میکنند، هکر یا مهاجم در اولین گام باید به دنبال ورودیهایی در برنامه باشد که درون یک درخواست SQL قرار گرفته باشند (مانند صفحات لاگین، ثبت نام، جستجو و ...).
کد زیر را در نظر بگیرید:
# Define POST variables uname = request.POST['username'] passwd = request.POST['password'] # SQL query vulnerable to SQLi sql = "SELECT id FROM users WHERE username='" + uname + "' AND password='" + passwd + "'" # Execute the SQL statement database.execute(sql)
اکنون ورودی password را برای نفوذ، تست میکنیم. مهاجم بدون داشتن نام کاربری، قصد دور زدن احراز هویت را دارد. بجای password عبارت زیر را قرار میدهد:
password' OR 1=1
در نهایت در بانک اطلاعاتی دستور زیر اجرا میشود:
SELECT id FROM users WHERE username='username' AND password= 'password' OR 1=1'
میدانیم که 1=1 است. پس بدون در نظر گرفتن اینکه شما برای username و password چه چیزی را وارد نمودید، عبارت درست در نظر گرفته میشود:
شرط اول and شرط دوم = نتیجه or 1=1 چون 1=1 است همیشه شرط کوئری درست خواهد بود
معمولا در بانک اطلاعاتی، اولین کاربری که وارد میکنند Administrator برنامه میباشد. پس به احتمال قوی شما میتوانید با مجوز ادمین به برنامه وارد شوید. البته میتوان با دانستن تنها نام کاربری هم بهراحتی با گذاشتن در قسمت username بدون دانستن password، به برنامه وارد شد؛ زیرا میتوان شرط چک کردن password را کامنت نمود:
-- MySQL, MSSQL, Oracle, PostgreSQL, SQLite ' OR '1'='1' -- ' OR '1'='1' /* -- MySQL ' OR '1'='1' # -- Access (using null characters) ' OR '1'='1' %00 ' OR '1'='1' %16
ابزارهایی برای تست آسیب پذیری SQLi
2) اسکنر اکانتیکس
چگونه از SQL Injection جلوگیری کنیم
1) روی دادههایی که از کاربر دریافت میگردد، اعتبار سنجی سمت کلاینت و سرور انجام شود. اگر فقط به اعتبارسنجی سمت کلاینت اکتفا کنید، هکر بهراحتی با استفاده از پروکسی، دادهها را تغییر میدهد. ورودیها را فیلتر و پاکسازی و با لیست سفید یا سیاه بررسی کنید ( ^ , ^, ^, ^ ).
2) از کوئریهایی که بدون استفاده از پارامتر از کاربر ورودی گرفته و درون یک درخواستSQL قرار میگیرند، اجتناب کنید:
[HttpGet] [Route("nonsensitive")] public string GetNonSensitiveDataById() { using (SqlConnection connection = new SqlConnection(_configuration.GetValue<string>("ConnectionString"))) { connection.Open(); SqlCommand command = new SqlCommand($"SELECT * FROM NonSensitiveDataTable WHERE Id = {Request.Query["id"]}", connection); using (var reader = command.ExecuteReader()) { if (reader.Read()) { string returnString = string.Empty; returnString += $"Name : {reader["Name"]}. "; returnString += $"Description : {reader["Description"]}"; return returnString; } else { return string.Empty; } } } }
با استفاده از پارامتر: (بهتر است نوع دیتا تایپ پارامتر و طول آن ذکر شود)
[HttpGet] [Route("nonsensitivewithparam")] public string GetNonSensitiveDataByNameWithParam() { using (SqlConnection connection = new SqlConnection(_configuration.GetValue<string>("ConnectionString"))) { connection.Open(); SqlCommand command = new SqlCommand($"SELECT * FROM NonSensitiveDataTable WHERE Name = @name", connection); command.Parameters.AddWithValue("@name", Request.Query["name"].ToString()); using (var reader = command.ExecuteReader()) { if (reader.Read()) { string returnString = string.Empty; returnString += $"Name : {reader["Name"]}. "; returnString += $"Description : {reader["Description"]}"; return returnString; } else { return string.Empty; } } } }
3) از Stored Procedureها استفاده کنید و بصورت پارامتری دادههای مورد نیاز را به آنها پاس دهید: (بهتر است نوع دیتا تایپ پارامتر و طول آن ذکر شود)
[HttpGet] [Route("nonsensitivewithsp")] public string GetNonSensitiveDataByNameWithSP() { using (SqlConnection connection = new SqlConnection(_configuration.GetValue<string>("ConnectionString"))) { connection.Open(); SqlCommand command = new SqlCommand("SP_GetNonSensitiveDataByName", connection); command.CommandType = System.Data.CommandType.StoredProcedure; command.Parameters.AddWithValue("@name", Request.Query["name"].ToString()); using (var reader = command.ExecuteReader()) { if (reader.Read()) { string returnString = string.Empty; returnString += $"Name : {reader["Name"]}. "; returnString += $"Description : {reader["Description"]}"; return returnString; } else { return string.Empty; } } } }
4) اگر از داینامیک کوئری استفاده میکنید، دادههای مورد استفادهی در کوئری را بصورت پارامتری ارسال کنید:
فرض کنید چنین جدولی دارید
CREATE TABLE tbl_Product ( Name NVARCHAR(50), Qty INT, Price FLOAT ) GO INSERT INTO tbl_Product (Name, Qty, Price) VALUES (N'Shampoo', 200, 10.0); INSERT INTO tbl_Product (Name, Qty, Price) VALUES (N'Hair Clay', 400, 20.0); INSERT INTO tbl_Product (Name, Qty, Price) VALUES (N'Hair Tonic', 300, 30.0);
یک پروسیجر را دارید که عملیات جستجو را انجام میدهد و از داینامیک کوئری استفاده میکند.
ALTER PROCEDURE sp_GetProduct(@Name NVARCHAR(50)) AS BEGIN DECLARE @sqlcmd NVARCHAR(MAX); SET @sqlcmd = N'SELECT * FROM tbl_Product WHERE Name = ''' + @Name + ''''; EXECUTE(@sqlcmd) END
با اینکه از Stored Procedure استفاده میکنید، باز هم در معرض خطر SQLi میباشید. فرض کنید هکر چنین درخواستی را ارسال میکند:
Shampoo'; DROP TABLE tbl_Product; --
نتیجه، تبدیل به دستور زیر میشود:
SELECT * FROM tbl_Product WHERE Name = 'Shampoo'; DROP TABLE tbl_Product; --'
برای جلوگیری از SQLi در کوئریهای داینامیک SP بشکل زیر عمل میکنیم:
ALTER PROCEDURE sp_GetProduct(@Name NVARCHAR(50)) AS BEGIN DECLARE @sqlcmd NVARCHAR(MAX); DECLARE @params NVARCHAR(MAX); SET @sqlcmd = N'SELECT * FROM tbl_Product WHERE Name = @Name'; SET @params = N'@Name NVARCHAR(50)'; EXECUTE sp_executesql @sqlcmd, @params, @Name; END
5) میتوان از تنظیمات IIS یا وب سرورهای دیگر برای جلوگیری از SQLi استفاده نمود.
6) استفاده از چند کاربرِ دیتابیس در برنامه و بکارگیری سطح دسترسی محدود و مناسب( ^ , ^ ).
7) از ORM استفاده کنید و اگر نیاز به سرعت بیشتری دارید از یک Micro ORM استفاده کنید؛ با در نظر داشتن نکات لازم.