مطالب
آشنایی با ساختار IIS قسمت دهم
در دو مقاله پیشین ( ^ و  ^ ) در مورد اینکه چگونه یک httpmodule یا httphandler را به عنوان یک ماژول جدید به IIS اضافه کنیم صحبت کردیم و الان قصد داریم که در این بخش این مبحث را ببندیم. آخرین بار توانستیم که یک UI را به IIS نسبت داده و از آن استفاده کنیم و الان قصد داریم آن را تکمیل‌تر کرده و کمی آن را شکیل‌تر کنیم. اولین نکته‌ای که توجه ما را جلب میکند این است که ماژول ما یک آیکن پیش فرض (چرخ دنده) دارد که بهتر است به آیکن دلخواه ما تغییر کند. به این منظور در پروژه، یک فایل Resource ایجاد کنید و یک تصویر را به این فایل Resource اضافه کنید و در کلاس imageCopyrightUI کد را به صورت زیر تغییر دهید:
internal class imageCopyrightUI : Module
    {
        protected override void Initialize(IServiceProvider serviceProvider, ModuleInfo moduleInfo)
        {
            
            base.Initialize(serviceProvider, moduleInfo);
            IControlPanel controlPanel = (IControlPanel)GetService(typeof(IControlPanel));
            ModulePageInfo modulePageInfo = new ModulePageInfo(this, typeof(imageCopyrightUIPage), "Image Copyright", "Image Copyright",Resource1.Visual_Studio_2012,Resource1.Visual_Studio_2012);
            controlPanel.RegisterPage(modulePageInfo);
        }
    }
حال پروژه را Rebuild کنید و IIS را مجددا باز کنید تا ببینید که کامپوننت جدید شما آیکن جدید را دارد. دومین نکته‌ای که به چشم می‌آید این است که اگر دقت کنید سایر ماژول‌ها یا کامپوننت‌ها هر کدام در گروهی جداگانه جای گرفته‌اند و افزونه‌ی شما در یک گروه به اسم others، که زیاد جالب نیست و شاید دوست داشته باشید که ماژول شما هم در یکی از همین دسته‌ها جای بگیرد یا حتی اینکه خودتان یک گروه جدید بسازید. برای اینکه خوب قسمت‌ها و نام کلاس‌های آن‌ها را یاد بگیرید به شکلی که در قسمت قبلی هم گذاشته بودیم خوب دقت کنید.

نکته ای که باید توجه داشته باشید این است که گروه بندی در IIS به سه شیوه صورت می‌گیرید : یکی به شیوه ناحیه Area مثل ASP.Net و management و IIS، دسته بندی Category مثل Application Development , ServerFeatures و بدون گروه بندی که می‌توانید از طریق لیست Group By در بالای پنجره IIS یکی از این سه حالت را انتخاب نمایید
برای اینکه ماژول در یک ناحیه یا دسته‌ای مشخص قرار بگیرد کد زیر را بنویسید:
  internal class imageCopyrightUI : Module
    {
        protected override void Initialize(IServiceProvider serviceProvider, ModuleInfo moduleInfo)
        {            
            base.Initialize(serviceProvider, moduleInfo);
            IControlPanel controlPanel = (IControlPanel)GetService(typeof(IControlPanel));
            ModulePageInfo modulePageInfo = new ModulePageInfo(this, typeof(imageCopyrightUIPage), "Image Copyright", "Image Copyright", Resource1.Visual_Studio_2012, Resource1.Visual_Studio_2012);
            controlPanel.RegisterPage(ControlPanelCategoryInfo.AspNet,modulePageInfo);
        }
    }
اگر خوب دقت کنید می‌پینید که تنها تغییر کدها در متد regiterpage بوده است که با استفاده از کلاس ControlPanelCategoryInfo ناحیه مورد نظر را مشخص کردیم و اگر حالا پروژه را Rebuild کنید می‌بینید که در ناحیه ASP.Net قرار گرفته است. گروه‌های‌های مختلف دیگری چون Application Development,Server,Performance و ... هم در این کلاس وجود دارند که جمعا 9 گروه می‌شوند.

 حال این سوال پیش می‌آید اگر بخواهم گروه اختصاصی ایجاد کنم، چه کاری باید انجام شود. پس کد را به شکل زیر تغییر دهید:
internal class imageCopyrightUI : Module
    {
        protected override void Initialize(IServiceProvider serviceProvider, ModuleInfo moduleInfo)
        {            
            base.Initialize(serviceProvider, moduleInfo);
            IControlPanel controlPanel = (IControlPanel)GetService(typeof(IControlPanel));
            ModulePageInfo modulePageInfo = new ModulePageInfo(this, typeof(imageCopyrightUIPage), "Image Copyright", "Image Copyright", Resource1.Visual_Studio_2012, Resource1.Visual_Studio_2012);

                        ControlPanelCategorization areaCategorization = null;
            foreach (ControlPanelCategorization categorization in controlPanel.Categorizations) {
                if (categorization.Key == ControlPanelCategorization.AreaCategorization) {
                    areaCategorization = categorization;
                    break;
                }
            }
           ControlPanelCategoryInfo cat=new ControlPanelCategoryInfo("dotnettips","Dot Net Tips","This is a Tutorial",areaCategorization);
            controlPanel.RegisterCategory(cat);
            controlPanel.RegisterPage(cat.Name,modulePageInfo);
        }
    }
در خطوط foreach ما به دنبال نوع گروهی که قرار است ماژول ما در آن قرار بگیرد می‌گردیم و علاقمندیم که گروه بندی ما در نوع Area باشد. برای همین این نوع را یافته و در متغیری از نوع ControlPanelCategorization قرار می‌دهیم. سپس توسط کلاس ControlPanelCategoryInfo یک نوع گروه بندی جدید ایجاد کرده ایم که به ترتیب نام، عنوان، توضیح و نهایتا نوع گروه بندی را در آن لحاظ می‌کنیم و سپس با دستور controlPanel.RegisterCategory گروه جدید خود را که در area قرار دارد، در IIS ثبت می‌کنیم و موقع افزودن صفحه مستقیما نام گروه را در آن ذکر می‌کنیم. پروژه را Rebuild و IIS را مجددا اجرا کنید. اگر Group by شما روی Area باشد که به طور پیش فرض چنین است شما باید گروه Dot Net Tips را ببینید؛ حال از طریق لیست Group By گزینه‌ی Category را انتخاب نمایید. همانطور که مشاهده می‌کنید دوباره ماژول شما در دسته‌ی Others قرار می‌گیرد؛ چرا که ما برای Area گروه بندی را لحاظ کرده بودیم. برای اینکه بتوانیم در دو حالت، دسته بندی داشته باشیم، کد را به شکل زیر تغییر می‌دهیم:
    ControlPanelCategorization areaCategorization = null;
                        ControlPanelCategorization CategoryCategorization = null;
            foreach (ControlPanelCategorization categorization in controlPanel.Categorizations) {
                if (categorization.Key == ControlPanelCategorization.AreaCategorization) {
                    areaCategorization = categorization;
                }
                if (categorization.Key == ControlPanelCategorization.CategoryCategorization)
                {
                    CategoryCategorization = categorization;
                }
            }
           ControlPanelCategoryInfo cat1=new ControlPanelCategoryInfo("dotnettipsarea","Dot Net Tips Area","This is a Tutorial",areaCategorization);
            controlPanel.RegisterCategory(cat1);
            controlPanel.RegisterPage(cat1.Name,modulePageInfo);

            ControlPanelCategoryInfo cat2 = new ControlPanelCategoryInfo("dotnettipscat", "Dot Net Tips Category", "This is a Tutorial", CategoryCategorization);
            controlPanel.RegisterCategory(cat2);
            controlPanel.RegisterPage(cat2.Name, modulePageInfo);
فکر کنم اتفاق بالا با توجه به مواردی که قبلا یاد گرفتید با وضوح کامل مشخص باشد که اینبار دو حالت گروه بندی را ذخیره و هر کدام را جداگانه در کنترل پنل IIS ثبت کردیم.
بین سایر گزینه‌های کنترل پنل گزینه controlpanel.regiterhomepage هم به چشم می‌خورد که اگر رابط کاربری را با این گزینه رجیستر کنیم صفحه اصلی IIS پریده و رابط کاربری ما جایگزینش می‌شود که البته این قسمت را باید با احتیاط با آن برخورد کرد وگرنه اگر بخواهیم همین رابط کاربری را به عنوان صفحه‌ی خانگی رجیستر کنیم، دسترسی ما به دیگر ماژول‌ها قطع خواهد شد.
تا به اینجا این مبحث از سری آموزشی ما بسته می‌شود. در مقالات آینده موارد دیگری از IIS را مورد بررسی قرار خواهیم داد.
مطالب
اتصال SQL Server به MySQL

اگر SQL Server و MySQL بر روی سیستم شما نصب است، روشی ساده برای انتقال اطلاعات بین این دو وجود دارد که نیازی به دخالت هیچ نوع برنامه‌ی جانبی نداشته و با امکانات موجود قابل مدیریت است.

ایجاد یک Linked server

برای اینکه SQL Server را به MySQL متصل کنیم می‌توان بین این دو یک Linked server تعریف کرد و سپس دسترسی به بانک‌های اطلاعاتی MySQL همانند یک بانک اطلاعاتی محلی SQL Server خواهد شد که شرح آن در ادامه ذکر می‌شود.
ابتدا نیاز است تا درایور ODBC مربوط به MySQL دریافت و نصب شود. آن‌را می‌توانید از اینجا دریافت کنید : (+)
سپس management studio را گشوده و در قسمت Server objects ، بر روی گزینه‌ی Linked servers کلیک راست نمائید. از منوی ظاهر شده، گزینه‌ی New linked server را انتخاب کنید:


در ادامه، باید تنظیمات زیر را در صفحه‌ی باز شده وارد کرد:


در قسمت Linked server و Product name ، نام دلخواهی را وارد کنید.
Provider انتخابی باید از نوع Microsoft OLE DB Provider for ODBC Drivers باشد.
مهم‌ترین تنظیم آن، قسمت Provider string است که باید به صورت زیر وارد شود (در غیر اینصورت کار نمی‌کند):
DRIVER={MySQL ODBC 5.1 Driver}; SERVER=localhost; DATABASE=testdb; USER=root; PASSWORD=mypass; OPTION=3;PORT=3306; CharSet=UTF8;
در اینجا نام دیتابیس پیش فرض، نام کاربری اتصال به MySQL و Password و غیره را می‌توان تنظیم کرد.
پس از انجام این تنظیمات بر روی دکمه‌ی Ok کلیک کنید تا Linked server ساخته شود:


اگر لیست بانک‌های اطلاعاتی را مشاهده نمودید، یعنی اتصال به درستی برقرار شده است.

تنظیمات ثانویه:

تا اینجا اس کیوال سرور به MySQL متصل شده است، اما برای استفاده بهینه از امکانات موجود نیاز است تا یک سری تغییرات دیگر را هم اعمال کرد.

تنظیم MSDASQL Provider :
در همان قسمت Linked provider ، ذیل قسمت Providers ، گزینه‌ی MSDASQL را انتخاب کرده و بر روی آن کلیک راست نمائید. سپس صفحه‌ی خواص آن‌را انتخاب کنید تا بتوان تنظیمات زیر را به آن اعمال کرد. این پروایدر جهت اتصال به MySQL مورد استفاده قرار می‌گیرد.



فعال سازی RPC :

برای اینکه بتوان از طریق SQL Server رکوردی را در یکی از جداول بانک‌های اطلاعاتی MySQL متصل شده ثبت نمود، می‌توان از دستور زیر استفاده کرد:
EXECUTE('insert into testdb.testtable(f1,f1) values(1,''data'')') at mysql

اینجا testdb نام بانک اطلاعاتی اتصالی MySQL است و testTable هم نام جدول مورد نظر. MySQL ایی که در آخر عبارت ذکر شده همان نام linked server ایی است که پیشتر تعریف کردیم.
به محض سعی در اجرای این کوئری خطای زیر ظاهر می‌شود:
Server 'mysql' is not configured for RPC.

برای رفع این مشکل، مجددا به صفحه‌ی خواص همان liked server ایجاد شده مراجعه کنید. در قسمت Server options دو گزینه مرتبط به RPC باید فعال شوند:



و اکنون برای کوئری گرفتن از اطلاعات ثبت شده هم از عبارت زیر می‌توان استفاده کرد:
SELECT * FROM OPENQUERY(mysql, 'SELECT * FROM testdb.testtable')

در این کوئری، MySQL نام Linked server ثبت شده است و testdb هم یکی از بانک‌های اطلاعاتی MySQL مورد نظر.


انتقال تمام اطلاعات یک جدول از بانک اطلاعاتی MySQL به SQL Server

پس از برقراری اتصال، اکنون import کامل یک جدول MySQL به SQL Server به سادگی اجرای کوئری زیر می‌باشد:
SELECT * INTO MyDb.dbo.testtable FROM openquery(MYSQL, 'SELECT * FROM testdb.testtable')

در این کوئری، MySQL همان Linked server تعریف شده است. MyDB نام بانک اطلاعاتی موجود در SQL Server جاری است و testtable هم جدولی است که قرار است اطلاعات testdb.testtable بانک اطلاعاتی MySQL به آن وارد شود.

با اطلاعات فارسی هم (در سمت SQL Server) مشکلی ندارد. همانطور که مشخص است، در اطلاعات provider string ذکر شده‌، مقدار charset به utf8 تنظیم شده و همچنین اگر نوع collation فیلدهای تعریف شده در MySQL نیز به utf8_persian_ci تنظیم شده باشد، با مشکل ثبت اطلاعات فارسی به صورت ???? مواجه نخواهید شد.


نکته:
اگر بانک اطلاعاتی MySQL شما بر روی local host نصب نیست، جهت فعال سازی دسترسی ریموت به آن، می‌توان به یکی از نکات زیر مراجعه کرد و سپس این اطلاعات جدید باید در همان قسمت provider string مرتبط با تعریف linked server وارد شوند:


مطالب مشابه:

هیچکدام از این روش‌ها قابل استفاده نبودند چون provider string صحیحی را نهایتا تولید نمی‌کنند. همچنین تمام این روش‌ها مبتنی است بر ایجاد DSN در کنترل پنل که اصلا نیازی به‌ آن نیست و اضافی است.

مطالب
محدود کردن دسترسی به اس کیوال سرور بر اساس IP

عموما محدود کردن دسترسی بر اساس IP بهتر است بر اساس راه حل‌هایی مانند فایروال، IPSec و یا RRAS IP Filter صورت گیرد که جزو بهینه‌ترین و امن‌ترین راه حل‌های ممکن هستند.
در ادامه قصد داریم این محدودیت را با استفاده از امکانات خود اس کیوال سرور انجام دهیم (بلاک کردن کاربران بر اساس IP های غیرمجاز). مواردی که در ادامه ذکر خواهند شد در مورد اس کیوال سرور 2005 ، سرویس پک 2 به بعد و یا اس کیوال سرور 2008 صادق است.
اس کیوال سرور این قابلیت را دارد که می‌توان بر روی کلیه لاگین‌های صورت گرفته در سطح سرور تریگر تعریف کرد. به این صورت می‌توان تمامی لاگین‌ها را برای مثال لاگ کرد (جهت بررسی مسایل امنیتی) و یا می‌توان هر لاگینی را که صلاح ندانستیم rollback نمائیم (ایجاد محدودیت روی لاگین در سطح سرور).

لاگ کردن کلیه لاگین‌های صورت گرفته به سرور

ایجاد جدولی برای ذخیره سازی اطلاعات لاگین‌ها:

USE [master]
GO

SET ANSI_NULLS ON
GO

SET QUOTED_IDENTIFIER ON
GO

SET ANSI_PADDING ON
GO

CREATE TABLE [dbo].[Logging](
[id] [int] IDENTITY(1,1) NOT NULL,
[LogonTime] [datetime] NULL,
[LoginName] [nvarchar](max) NULL,
[ClientHost] [varchar](50) NULL,
[LoginType] [varchar](100) NULL,
[AppName] [nvarchar](500) NULL,
[FullLog] [xml] NULL,
CONSTRAINT [PK_IP_Log] PRIMARY KEY CLUSTERED
(
[id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]

GO

SET ANSI_PADDING OFF
GO

ALTER TABLE [dbo].[Logging] ADD CONSTRAINT [DF_IP_Log_LogonTime] DEFAULT (getdate()) FOR [LogonTime]
GO

در ادامه یک تریگر لاگین را جهت ذخیره سازی اطلاعات کلیه لاگین‌ها به سرور ایجاد می‌نمائیم:
USE [master]
GO

CREATE TRIGGER LogonTrigger
ON ALL SERVER
FOR LOGON
AS
BEGIN
DECLARE @data XML
SET @data = EVENTDATA()

INSERT INTO [Logging]
(
[LoginName],
[ClientHost],
[LoginType],
[AppName],
[FullLog]
)
VALUES
(
@data.value('(/EVENT_INSTANCE/LoginName)[1]', 'nvarchar(max)'),
@data.value('(/EVENT_INSTANCE/ClientHost)[1]', 'varchar(50)'),
@data.value('(/EVENT_INSTANCE/LoginType)[1]', 'varchar(100)'),
APP_NAME(),
@data
)
END
اکنون برای مثال از آخرین 100 لاگین انجام شده، به صورت زیر می‌توان گزارشگیری کرد:

SELECT TOP 100 * FROM [master].[dbo].[Logging] ORDER BY id desc
و بدیهی است در تریگر فوق می‌توان روی هر کدام از آیتم‌های دریافتی مانند ClientHost و غیره فیلتر ایجاد کرد و تنها موارد مورد نظر را ثبت نمود.

محدود کردن کاربران بر اساس IP

ClientHost ایی که در رخ‌داد لاگین فوق بازگشت داده می‌شود همان IP کاربر راه دور است. برای فیلتر کردن IP های غیرمجاز، ابتدا در دیتابیس مستر یک جدول برای ذخیره سازی IP های مجاز ایجاد می‌کنیم و IP های کلیه کلاینت‌های معتبر خود را در آن وارد می‌کنیم:

USE [master]
GO
CREATE TABLE [IP_RESTRICTION](
[ValidIP] [varchar](15) NOT NULL,
CONSTRAINT [PK_IP_RESTRICTION] PRIMARY KEY CLUSTERED
(
[ValidIP] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]

سپس تریگر لاگین ما برای منع کاربران غیرمجاز بر اساس IP ، به صورت زیر خواهد بود:

USE [master]
GO

CREATE TRIGGER [LOGIN_IP_RESTRICTION]

ON ALL SERVER
FOR LOGON
AS
BEGIN
DECLARE @host NVARCHAR(255);
SET @host = EVENTDATA().value('(/EVENT_INSTANCE/ClientHost)[1]', 'nvarchar(max)');

IF (
NOT EXISTS(
SELECT *
FROM MASTER.dbo.IP_RESTRICTION
WHERE ValidIP = @host
)
)
BEGIN
ROLLBACK;
END
END;
اخطار مهم!
تریگر فوق خطرناک است! ممکن است خودتان هم دیگر نتوانید لاگین کنید!! (حتی با اکانت ادمین)
بنابراین قبل از لاگین حتما IP لوکال و یا ClientHost لوکال را هم وارد کنید.
اگر گیر افتادید به صورت زیر می‌شود رفع مشکل کرد:
تنها حالتی که تریگر لاگین را فعال نمی‌کند Dedicated Administrator Connection است یا DAC هم به آن گفته می‌شود. به صورت پیش فرض برای ایجاد این اتصال اختصاصی باید به کامپیوتری که اس کیوال سرور بر روی آن نصب است به صورت لوکال لاگین کرد و سپس در خط فرمان دستور زیر را صادر کنید (حرف A آن باید بزرگ باشد):

C:\>sqlcmd -A -d master -q "insert into IP_RESTRICTION(validip) values('<local machine>')"
به این صورت local machine به جدول IP های مجاز اضافه شده و می‌توانید لاگین کنید!
این نوع تریگرها در قسمت server objects در management studio ظاهر می‌شوند.

مطالب
یافتن لیست اسمبلی‌های ارجاعی

اگر برنامه‌ی شما برای مثال با SMO مربوط به اس کیوال سرور 2008 کامپایل شود، روی سروری با SQL Server 2005 کار نخواهد کرد و پیغام می‌دهد که نگارش 10 اسمبلی Microsoft.SqlServer.Management.Sdk.Sfc یافت نشد.
یک راه حل آن، نصب Microsoft SQL Server 2008 Management Objects بر روی سرور است، یا راه حل دوم، پیدا کردن اسمبلی‌هایی که برنامه به آن‌ها ارجاع دارد و کپی کردن آن‌ها کنار فایل اجرایی برنامه در سرور. (درست کردن یک برنامه پرتابل دات نتی، یا نسبتا پرتابل!)
برای این منظور کلاس زیر تهیه شده است که مسیر فایل اجرایی یا dll یک پروژه را دریافت کرده و لیست تمام ارجاعات به آن‌را به صورت بازگشتی پیدا می‌کند. (البته در قسمت یافتن مسیر اسمبلی‌ها، اسمبلی‌های سیستمی که با خود دات نت فریم ورک نصب می‌شوند، حذف شده است)

using System.Collections.Generic;
using System.Reflection;
using System.IO;

namespace App
{
class CFindRef
{
#region Fields (2)

/// <summary>
/// لیستی جهت نگهداری نام اسمبلی‌ها
/// </summary>
private readonly List<string> _assemblies = new List<string>();
/// <summary>
/// لیستی جهت نگهداری مسیر اسمبلی‌ها
/// </summary>
private readonly List<string> _filePath = new List<string>();

#endregion Fields

#region Constructors (1)

/// <summary>
/// سازنده کلاس
/// </summary>
/// <param name="fileName">مسیر اولیه اسمبلی مورد نظر</param>
public CFindRef(string fileName)
{
ListReferences(fileName);
}

#endregion Constructors

#region Properties (2)

/// <summary>
/// لیست مسیر اسمبلی‌هایی که به آن‌ها ارجاعی وجود دارد منهای موارد سیستمی
/// </summary>
public List<string> ReferencedFiles
{
get
{
_filePath.Sort();
return _filePath;
}
}

/// <summary>
/// لیست کامل اسمبلی‌هایی که اسمبلی ما به آن‌ها وابسته است
/// </summary>
public List<string> ReferencedNames
{
get
{
_assemblies.Sort();
return _assemblies;
}
}

#endregion Properties

#region Methods (1)

// Private Methods (1)

/// <summary>
/// متدی بازگشتی جهت یافتن لیست ارجاعات
/// </summary>
/// <param name="path">مسیر یا نام اسمبلی</param>
private void ListReferences(string path)
{
//آیا تکراری است؟
if (_assemblies.Contains(path))
return;

Assembly asm;
// آیا فایل است یا نام کامل اسمبلی
if (File.Exists(path))
{
// load the assembly from a path
asm = Assembly.LoadFrom(path);
}
else
{
// سعی در بارگذاری اسمبلی
try
{
asm = Assembly.Load(path);
}
catch
{
asm = null; //جای بهبود دارد
}
}

if (asm == null) return;

// افزودن به لیست نام‌ها
_assemblies.Add(path);
string asmLocation = asm.Location;
//حذف موارد سیستمی از لیست مسیر فایل‌ها
if (asmLocation != null && !asmLocation.Contains("\\System.")
&& !asmLocation.Contains("\\mscorlib"))
_filePath.Add(asmLocation);

// پیدا کردن ارجاع‌ها
AssemblyName[] imports = asm.GetReferencedAssemblies();
// iterate
foreach (AssemblyName asmName in imports)
{
// فراخوانی بازگشتی جهت یافتن تمامی ارجاعات
ListReferences(asmName.FullName);
}
}

#endregion Methods
}
}

مثالی در مورد نحوه‌ی استفاده از آن:

CFindRef cfr = new CFindRef(@"C:\App\test.exe");
foreach (var asmRef in cfr.ReferencedFiles)
{
textBox1.Text += asmRef + Environment.NewLine;
//Application.DoEvents();
}
برای نمونه، برنامه‌ای که از SMO‌ مربوط به اس کیوال سرور 2008 استفاده می‌کند، این لیست ارجاعات را دارد:

C:\WINDOWS\assembly\GAC_MSIL\Microsoft.SqlServer.ConnectionInfo\10.0.0.0__89845dcd8080cc91\Microsoft.SqlServer.ConnectionInfo.dll
C:\WINDOWS\assembly\GAC_MSIL\Microsoft.SqlServer.Dmf\10.0.0.0__89845dcd8080cc91\Microsoft.SqlServer.Dmf.dll
C:\WINDOWS\assembly\GAC_MSIL\Microsoft.SqlServer.Management.Sdk.Sfc\10.0.0.0__89845dcd8080cc91\Microsoft.SqlServer.Management.Sdk.Sfc.dll
C:\WINDOWS\assembly\GAC_MSIL\Microsoft.SqlServer.ServiceBrokerEnum\10.0.0.0__89845dcd8080cc91\Microsoft.SqlServer.ServiceBrokerEnum.dll
C:\WINDOWS\assembly\GAC_MSIL\Microsoft.SqlServer.Smo\10.0.0.0__89845dcd8080cc91\Microsoft.SqlServer.Smo.dll
C:\WINDOWS\assembly\GAC_MSIL\Microsoft.SqlServer.SqlClrProvider\10.0.0.0__89845dcd8080cc91\Microsoft.SqlServer.SqlClrProvider.dll
C:\WINDOWS\assembly\GAC_MSIL\Microsoft.SqlServer.SqlEnum\10.0.0.0__89845dcd8080cc91\Microsoft.SqlServer.SqlEnum.dll
با کپی کردن همین فایل‌ها کنار فایل اجرایی برنامه‌ای که قرار است روی سروری با SQL Server 2005 کار کند، مشکل برطرف می‌شود و برنامه بدون مشکل کار خواهد کرد.

برنامه آماده هم در این زمینه موجود است، برای مثال CheckAsm
مشاهده سایت

مطالب
تاریخچه‌ی نگارش‌های مختلف دات نت فریم ورک

حدود 8 سال از ارائه اولین نگارش دات نت فریم ورک می‌گذرد و در ادامه مرور سریعی خواهیم داشت بر عناوین کتابخانه‌های اضافه شده به این مجموعه:



دات نت فریم ورک 1.0
اولین ارائه عمومی آزمایشی آن در PDC 2000 صورت گرفت و در اوایل 2002 به عموم عرضه شد (2/13/2002).
عبارت کد مدیریت شده را به دنیا معرفی کرد (managed code) و شامل اجزای زیر بود:
• GC, JIT
• C#
• Coherent Framework
• XSP….ASP+…ASP.NET!
• WinForms

دات نت فریم ورک 1.1
در اوایل 2003 به همراه ویندوز سرور 2003 و VS 2003 ارائه شد.
• Mobile ASP.NET controls
• Built-in support for ODBC and Oracle databases.
• IPv6 support.

دات نت فریم ورک 2
در اواخر 2005 ارائه شد (11/07/2005) و شامل تازه‌های زیر بود:
• ASP.NET for the Masses
○ Application Building Blocks
§ Parts, Authentication, Role Management, etc
○ Visual Web Developer
• Client Development
• ClickOnce!

دات نت فریم ورک 3
در اواخر 2006 ارائه شد (11/06/2006) و موارد زیر را به این فریم ورک افزود:

• Windows CardSpace - Digital identity interface.
• Windows Presentation Foundation : WPF
○ Vector Graphics, Media and UI
○ Enters the age of UX
• Windows Communication Foundation : WCF
○ Unified messaging model
• Windows Workflow Foundation : WF
○ Coordinating work with durable applications

دات نت فریم ورک 3.5
در پایان 2007 ارائه شد (11/19/2007) و تازه‌های زیر را به همراه داشت:
• Linq
• Expression Trees and Lamda Methods
• Extension Methods
• Paging Support for ADO.NET
• Managed Wrappers for WMI and AD
• Enhancements to WCF and WF
• System.CodeDom namespace
• ASP.NET AJAX
• WCF/WF
○ REST Services
○ Workflow Services
• Client
○ Sync
○ Client app services

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

سرویس پک یک دات نت فریم ورک 3.5
در اواسط 2008 ارائه شد (8/11/2008) و به همراه تغییرات زیر بود:
• ASP.NET Dynamic Data
• ADO.NET
○ Entity Framework
○ Data Services (Astoria)
• WCF
○ AtomPub ServiceDocuments
• Client
○ Client Profile
○ Performance
§ Working set and startup time
• Silverlight 2
○ RTM end of 2008
○ Brings power of .NET to the web client
○ Media and RIA .NET platform


دات نت فریم ورک 4.0
نگارش بتای آن در دسترس است و احتمالا نگارش نهایی آن در سال آینده‌ی میلادی به همراه VS2010 ارائه می‌شود. این مجموعه تازه‌های زیر را به همراه خواهد داشت:

• Base Class Library Improvements
○ Managed Extensibility Framework
○ More Core Data Structures
○ I/O Improvements
• Parallel Computing
○ Task Parallel Library
○ Parallel Linq (PLINQ)
○ Coordination Data Structures (CDS)
• Client
○ WPF
§ Client Profile
§ Business Focused Controls
§ Win7 Advances (Multi-touch, etc)
○ ADO.NET
§ Entity Framework v2
□ Code-First Development
□ TDD Support
□ Foreign-Key Support
○ ASP.NET
§ ASP.NET Dynamic Data Improvements
§ ASP.NET MVC
§ ASP.NET Dynamic Data for MVC
§ Extensible Caching Framework
○ WF & WCF
§ Fully Declarative Services
§ Workflow Enhancements
□ New flowchart modeling
□ Workflow Rules Integration
□ … much more…
§ WCF Enhancements
□ Durable Duplex
□ WS-Discovery & UDP Channel
□ In-Process Channel
§ RIA (Silverlight)
□ Simplified N-tier development
□ Business-focused framework

مطالب
مونیتور کردن میزان مصرف CPU در اس کیوال سرور

در این مقاله قصد داریم نحوه مونیتور کردن میزان مصرف CPU توسط اس کیوال سرور را بررسی کنیم. برای بدست آوردن میزان CPU مصرفی اس کیوال سرور می‌توان به اسکریپت زیر رجوع کرد:

DECLARE @CPU_BUSY int, @IDLE int
SELECT @CPU_BUSY = @@CPU_BUSY, @IDLE = @@IDLE WAITFOR DELAY '000:00:01'
SELECT (@@CPU_BUSY - @CPU_BUSY)/((@@IDLE - @IDLE + @@CPU_BUSY - @CPU_BUSY) *1.00) *100 AS 'CPU Utilization by sqlsrvr.exe'

ماخذ

در ادامه قصد داریم، هر 5 دقیقه به صورت خودکار بررسی کنیم که آیا میزان مصرف CPU در اس کیوال سرور بالای 50 درصد است؟ و اگر بله، ایمیلی را به مسؤول مربوطه جهت بررسی ارسال کنیم.

بنابراین اولین کاری که باید صورت گیرد، فعال سازی Database Mail در اس کیوال سرور است که به صورت پیش فرض غیرفعال است. برای این منظور تنها کافی است اسکریپت زیر را بر روی سرور اجرا کنید:

USE [master]
GO
sp_configure 'show advanced options',1
GO
RECONFIGURE WITH OVERRIDE
GO
sp_configure 'Database Mail XPs',1
GO
--RECONFIGURE
GO
-- Create a New Mail Profile for Notifications
EXECUTE msdb.dbo.sysmail_add_profile_sp
@profile_name = 'DBA_Notifications',
@description = 'Profile for sending Automated DBA Notifications'
GO
-- Set the New Profile as the Default
EXECUTE msdb.dbo.sysmail_add_principalprofile_sp
@profile_name = 'DBA_Notifications',
@principal_name = 'public',
@is_default = 1 ;
GO
-- Create an Account for the Notifications
EXECUTE msdb.dbo.sysmail_add_account_sp
@account_name = 'SQLMonitor',
@description = 'Account for Automated DBA Notifications',
@email_address = 'nasiri@site.net', -- Change This
@display_name = 'SQL Monitor',
@mailserver_name = 'mail.site.net' -- Change This
GO
-- Add the Account to the Profile
EXECUTE msdb.dbo.sysmail_add_profileaccount_sp
@profile_name = 'DBA_Notifications',
@account_name = 'SQLMonitor',
@sequence_number = 1

GO

ماخذ

این اسکریپت برای اس کیوال سرورهای 2005 به بعد طراحی شده و تنها دو سطر آن‌را پیش از اجرا باید ویرایش کنید. سطر مربوط به email_address و mailserver_name . آدرس ایمیل درحقیقت آدرس ایمیل قسمت from پیغام ارسالی را تشکیل می‌دهد. نام سرور میل هم، منظور آدرس smtp server شما در شبکه است.

یا اگر علاقمند بودید که این‌کار را توسط ویزاردهای management studio انجام دهید (که در نهایت هیچ تفاوتی با اسکریپت فوق نخواهد داشت)، می‌توان به این مقاله رجوع کرد.

پس از اجرای اسکریپت فوق، برای بررسی صحت عملکرد فوق می‌توان دستور زیر را اجرا کرد:

--test
EXECUTE msdb.dbo.sp_send_dbmail
@recipients = 'nasiri@site.net', -- Change This
@Subject = 'Test Message generated from SQL Server DatabaseMail',
@Body = 'This is a test message from SQL Server DatabaseMail'

سایر پارامترهای این دستور را در MSDN می‌توان ملاحظه نمود.

تا اینجا اس کیوال سرور برای ارسال ایمیل آماده شد. در ادامه قصد داریم یک job جدید در اس کیوال سرور ایجاد کنیم تا تمام موارد فوق را لحاظ کند.


مطابق تصویر فوق ابتدا یک job جدید را آغاز خواهیم کرد.



در ادامه اسکریپت زیر را جهت اجرا به آن معرفی می‌کنیم. توسط این اسکریپت، میزان جاری مصرف CPU اس کیوال سرور محاسبه شده و اگر این میزان بیشتر از 50 بود، یک ایمیل به مسؤول مربوطه با ذکر میزان CPU usage ارسال می‌گردد.

DECLARE @CPUUsage INT
DECLARE @CPU_BUSY INT,
@IDLE INT

SELECT @CPU_BUSY = @@CPU_BUSY,
@IDLE = @@IDLE

WAITFOR DELAY '000:00:01'
SELECT @CPUUsage = (@@CPU_BUSY - @CPU_BUSY) / ((@@IDLE - @IDLE + @@CPU_BUSY - @CPU_BUSY) * 1.00)
* 100 -- CPU Utilization by sqlsrvr.exe


IF @CPUUsage > 50
BEGIN
DECLARE @msg NVARCHAR(1000)
SET @msg = 'Please check SQL server, CPU usage is ' + CAST(@CPUUsage AS NVARCHAR(50))
+ '%.'

EXECUTE msdb.dbo.sp_send_dbmail
@recipients = 'nasiri@site.net', -- Change This
@copy_recipients = 'nasiri@site.net', -- Change This
@Subject = 'CPU overload',
@Body = @msg
,@importance = 'High'
END



و در آخر زمان اجرای آن را به هر روز، هر 5 دقیقه یکبار تنظیم خواهیم کرد.

اگر نیاز به راه حلی پخته‌تر و بررسی متوسط چندین مقدار قبلی ، مقایسه آن‌ها و سپس ارسال ایمیل داشتید، می‌توان به فصل 14 کتاب Super SQL Server Systems مراجعه کرد.

اشتراک‌ها
Visual Studio 2019 version 16.3.3 منتشر شد
Visual Studio 2019 version 16.3.3 منتشر شد
اشتراک‌ها
Visual Studio 2019 version 16.2.1 منتشر شد
نظرات مطالب
سفارشی سازی ASP.NET Core Identity - قسمت سوم - نرمال سازها و اعتبارسنج‌ها
تغییرات مطابق موارد زیر داده شد ولی باز هم همان ارور 
public class CustomNormalizer : ILookupNormalizer
    {
        public string NormalizeName(string key)
        {
            key = Normalize(key);
            key = key.ApplyCorrectYeKe()
                     .RemoveDiacritics()
                     .CleanUnderLines()
                     .RemovePunctuation();
            key = key.Trim().Replace(" ", "");
            return key;
        }
        public string NormalizeEmail(string key)
        {
            key = Normalize(key);
            key = fixGmailDots(key);
            return key;
        }
        public string Normalize(string key)
        {
            if (string.IsNullOrWhiteSpace(key))
            {
                return null;
            }

            key = key.Trim();
            key = key.ToUpperInvariant();
            return key;
        }

        private static string fixGmailDots(string email)
        {
            email = email.ToLowerInvariant().Trim();
            var emailParts = email.Split('@');
            var name = emailParts[0].Replace(".", string.Empty);

            var plusIndex = name.IndexOf("+", StringComparison.OrdinalIgnoreCase);
            if (plusIndex != -1)
            {
                name = name.Substring(0, plusIndex);
            }

            var emailDomain = emailParts[1];
            emailDomain = emailDomain.Replace("googlemail.com", "gmail.com");

            string[] domainsAllowedDots =
            {
                "gmail.com",
                "facebook.com"
            };

            var isFromDomainsAllowedDots = domainsAllowedDots.Any(domain => emailDomain.Equals(domain));
            return !isFromDomainsAllowedDots ? email : string.Format("{0}@{1}", name, emailDomain);
        }
    }

اینترفیس ILookupNormalizer   به شکل زیر است

#region Assembly Microsoft.Extensions.Identity.Core, Version=3.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60
// C:\Users\kazemi\.nuget\packages\microsoft.extensions.identity.core\3.0.0-preview-18579-0056\lib\netstandard2.0\Microsoft.Extensions.Identity.Core.dll
#endregion

namespace Microsoft.AspNetCore.Identity
{
    //
    // Summary:
    //     Provides an abstraction for normalizing keys for lookup purposes.
    public interface ILookupNormalizer
    {
        //
        // Summary:
        //     Returns a normalized representation of the specified key.
        //
        // Parameters:
        //   key:
        //     The key to normalize.
        //
        // Returns:
        //     A normalized representation of the specified key.
        string Normalize(string key);
    }
}

نظرات مطالب
ساخت ربات تلگرامی با #C
1. آپدیتهای دریافتی همیشه فقط شامل Message نمیشه و ممکنه آپدیت دریافتی از نوع CallbackQuery هم باشه
2. کمی بالاتر توضیح داده شده که برای دریافت آپدیت‌های جدید باید پارامتر offset رو هم ارسال کنی. مقدار این پارامتر باید رقم بعدی Id آخرین آپدیت دریافتی باشه یعنی update_id  +1
برای طراحی ربات تلگرام هم بهتر هست که از پکیج‌های آماده استفاده بشه که بالاتر عرض کردم.
یه پروژه کنسول ایجاد کن پکیج telegram.bot رو هم از Nuget به برنامه اضافه کن و کلاس program.cs  رو به صورت زیر پیاده کن
به جای BOT_TOKEN هم توکن ربات خودت رو کپی کن و برنامه رو اجرا کن
using System.Threading.Tasks;
using Telegram.Bot;

namespace Bot.Engine.Console
{
    public class Program
    {
        Api bot;
        string botToken = "BOT_TOKEN";

        public static void Main(string[] args)
        {
            Task.Run(() => RunBot(botToken));

            System.Console.ReadLine();
        }


        /// <summary>
        /// 
        /// </summary>
        public static async Task RunBot(string botToken)
        {
            #region راه اندازی ربات

             bot = new Api(botToken);
            var me = await bot.GetMe();
            if (me != null)
            {
                System.Console.WriteLine("bot started {0}", me.Username);
            }
            else
            {
                System.Console.WriteLine("get bot failed ");
            }

          
            #endregion

            #region شروع گوش دادن به درخواست‌ها var whileCount = 0;
            var offset = 0;

            while (true)
            {
                System.Console.WriteLine("while no {0}", whileCount);

                whileCount += 1;
                try
                {
                    var updates = await bot.GetUpdatesAsync(offset);
                    var updatesCount = updates.Count();
                    System.Console.WriteLine("updates count is {0}", updatesCount);
                    System.Console.WriteLine("================================================================");

                    if (updatesCount > 0)
                    {
                        foreach (var update in updates)
                        {
                            try
                            {
                                offset = update.Id + 1;
                                if (update.Message.Text!=null)
                                {
                                    //echo msg
                                    await bot.SendTextMessageAsync(update.Message.Chat.Id, update.Message.Text);
                                }
                                else
                                {
                                    await bot.SendTextMessageAsync(update.Message.Chat.Id, "لطفا یک پیام متنی بفرستید");
                                }

                            }
                            catch (Exception ex)
                            {
                                bot.SendTextMessage(update.Message.Chat.Id, ex.ToString());
                            }
                        }
                        continue;
                    }


                }
                catch (Exception ex)
                {
                    System.Console.WriteLine("Error Msg = {0}",ex.Message);
                }

            }

            #endregion
        }

    }
}