claims.Add(new Claim(UserClaimTypes.PackedPermission, new[] {"48", "65", "6C", "6C", "6F", "20", "57", "6F", "72", "6C", "64", "21"} .PackPermissionsToString()));
این سناریو رو در نظر بگیرید:
وب سرور ما در همان محلی قرار دارد که SVN Server نصب شده است.
میخواهیم به ازای هربار Commit تیم به مخزن SVN ما، سایت ارائه شده توسط وب سرور نیز به صورت خودکار به روز شود.
چه باید کرد؟!
احتمالا خیلیها تصور میکنند که امکان پذیر نیست؛ چون مخزن SVN موجود در سرور، ساختار خودش را دارد و همانند فایلهای یک پروژه معمولی نگهداری نمیشود.
برای انجام اینکار چندین روش موجود است، که تمام آنها به مفهوم hooks در SVN گره خورده است. هرچند hook به معنای قلاب است، اما در اینجا معنای تریگر را دارد. شبیه به تریگرهای SQL Server : پیش یا پس از انجام کار یا رخداد مشخصی، فلان کار را انجام بده. (برای اطلاعات بیشتر میتوانید به فصل hooks در این کتابچه مراجعه کنید: (+))
در میان این قلابهای موجود، میتوان از قلاب post-commit جهت به روز رسانی یک سایت پس از هر هماهنگ سازی با مخزن SVN استفاده کرد. پیشنهاد من به تمام کسانی که میخواهند کار با SVN را شروع کنند استفاده از برنامه رایگان Visual SVN Server است. این برنامه سازگاری فوق العادهای با محیط ویندوز دارد (از لحاظ تعریف سطح دسترسیها). همچنین تعریف hooks را هم به شدت ساده کرده است. فقط کافی است روی یک مخزن کد تعریف شده در Visual SVN Server کلیک راست کرده و در برگهی باز شده، تنظیمات سطوح دسترسی یا تعاریف Hooks را اضافه نمود (در اینجا اعمال سطوح دسترسی روی پوشهها یا روی فایلها نیز به همان شکل با کلیک راست و کم و زیاد کردن کاربران میسر است؛ همانند دادن دسترسی بر اساس امکانات NTFS و اکتیودایرکتوری).
بنابراین به صورت خلاصه:
- فرض بر این است که مخزن کد SVN ایی را بر روی سرور راه اندازی کردهاید. همچنین پوشهای را که میخواهید ریشه سایت باشد، مثلا در مسیر دلخواه C:\path\www قرار دارد.
- برای شروع کار، check out باید صورت گیرد. یا میتوان از TortoiseSVN استفاده کرد یا چون مخزن کد در همان سرور است، دستور زیر نیز کار میکند:
svn checkout file:///c:/svn/MyRepository/trunk C:\path\www
- سپس یک فایل bat باید درست کنید با محتوای زیر:
svn update file:///c:/svn/MyRepository/trunk C:\path\www
این فایل bat باید در همان قسمت تعریف post-commit hook استفاده شود.
به این معنا که پس از هر commit ، لطفا مسیر C:\path\www را بر اساس آخرین به روز رسانیهای مخزن کد به صورت خودکار به روز کن. در این حالت اگر فایلی حذف شده باشد، به صورت خودکار از ریشه سایت شما حذف میشود و اگر فایل یا فایلهایی تغییر کرده باشند نیز سریعا به روز رسانی آنها انجام خواهد شد.
در روش svn update ، پوشههای مخفی svn نیز در ریشه سایت حضور خواهند داشت. وجود آنها هم الزامی است زیرا update بر همین اساس کار میکند.
- اگر میخواهید این پوشههای مخفی وجود نداشته باشند از دستور svn export استفاده کنید. فقط دقت کنید که در این حالت اگر فایلی از مخزن کد حذف شده باشد، باز هم در ریشه سایت وجود خواهد داشت. راه حلی هم که توصیه شده، این است که در همان bat فایلی که درست میکنید ابتدا دستور حذف محتویات پوشه ریشه را صادر کنید و بعد svn export . البته بدیهی است این روش نسبت به svn update کندتر است و svn update به شدت بهینه و سریع میباشد.
- یا راه دیگر بجای حذف کردن پوشه موجود و بعد export به آن، استفاده از برنامههایی مانند Robocopy است که میتوانند عملیات همگام سازی را هم انجام دهند. در این حالت محتوای فایل bat شما شبیه به دستورات زیر خواهد شد:
svn checkout file:///c:/svn/MyRepository/trunk C:\temp\Site1 >> output.log
robocopy C:\temp\Site1 C:\path\www *.* /S /XF *.cs *.tmp *.sln *.csproj *.webinfo /XD .svn _svn /PURGE >> output.log
به این معنا که پس از هر commit به مخزن کد (با توجه به تعریف قلاب ذکر شده)، ابتدا یک svn checkout در یک پوشه موقتی (خارج از ریشه اصلی سایت) انجام گردیده و سپس برنامه robocopy یا موارد مشابه آن وارد عمل شده و تغییرات را با ریشه اصلی هماهنگ میکنند (در اینجا میتوان مشخص کرد چه فایلهایی با پسوندهای مشخص، با ریشه سایت هماهنگ نشوند).
در کل همان روش svn update به نظر سریعتر و مقرون به صرفهتر است. اگر از IIS استفاده میکنید، به صورت پیش فرض کسی نمیتواند محتوای پوشهای را با وارد کردن آدرس آن در مرورگر بررسی کند، همچنین IIS فایلهایی را که نمیشناسد (پسوند از پیش تعریف شدهای در بانک اطلاعاتی آن ندارند)، سرو نمیکند و در صورت درخواست آنها، خطای 404 یا "پیدا نشد" به کاربر نهایی ارائه خواهد شد.
کدهای این قسمت بهروزرسانی شده و از این ریپازیتوری قابل دسترسی است.
Event Sourcing
در این قسمت قصد داریم تا اطلاعات Commandهای خود را بعد از Process، داخل یک دیتابیس Append-Only ذخیره کنیم. با استفاده از این روش میتوانیم بفهمیم در یک تاریخ مشخص، با چه ورودیهایی ( Request )، چه جواب ( Response ) ای در آن لحظه از برنامه برگشت داده شدهاست.
برای پیاده سازی Event Sourcing از دیتابیس EventStore که سورس آن نیز در گیتهاب قابل دسترسی است، استفاده خواهیم کرد. توجه داشته باشید که شما میتوانید از دیتابیسهای دیگری مثل Elasticsearch, Redis و ... بهمنظور دیتابیس Event Store خود استفاده کنید و محدود به EventStore نیستید.
ما برای راه اندازی دیتابیس EventStore در این قسمت، از Docker استفاده خواهیم کرد. آموزش Docker قبلا طی مقالاتی (2 , 1) در سایت قرار گرفتهاست و در این مقاله به تکرار نحوه استفاده از آن نخواهیم پرداخت.
با استفاده از دستور زیر، EventStore را از روی Docker Hub که Registry پیشفرض است، Pull و اجرا میکنیم و پورتهای 2113 و 1113 آن را به بیرون Expose میکنیم تا داخل برنامه خود، از آنها استفاده کنیم:
docker run --name eventstore-node -d -p 2113:2113 -p 1113:1113 eventstore/eventstore
EventStore دارای پنل ادمینی است که از طریق http://localhost:2113 قابل دسترسی است. Username پیشفرض آن برابر با admin و کلمه عبور آن برابر با changeit است.
بعد از لاگین در پنل ادمین، با چنین Dashboard ای مواجه خواهید شد و نشان از این دارد که EventStore بهدرستی اجرا شده است:
برای استفاده از EventStore داخل برنامه خود، مانند دیگر دیتابیسها، Client موجود آن را برای #C، از NuGet نصب میکنیم:
Install-Package EventStore.Client
سپس کلاسی بنام EventStoreDbContext ایجاد و منطق ارتباط با EventStore را داخل آن قرار میدهیم :
public class EventStoreDbContext : IEventStoreDbContext { public async Task<IEventStoreConnection> GetConnection() { IEventStoreConnection connection = EventStoreConnection.Create( new IPEndPoint(IPAddress.Loopback, 1113), nameof(MediatrTutorial)); await connection.ConnectAsync(); return connection; } public async Task AppendToStreamAsync(params EventData[] events) { const string appName = nameof(MediatrTutorial); IEventStoreConnection connection = await GetConnection(); await connection.AppendToStreamAsync(appName, ExpectedVersion.Any, events); } }
همانطور که میبینید، با استفاده از IP 1113 که در بالاتر با استفاده از Docker آن را Expose کرده بودیم، به EventStore متصل شدهایم. همچنین برای متد AppendToStreamAsync خود EventStore ، یک Facade نوشتهایم که نحوه کار با آن را برایمان راحتتر کردهاست.
با توجه به اینکه EventStore در Documentation خود بیان کرده که Thread-Safe است، در DI Container خود، EventStoreDbContext را بصورت Singleton ثبت و Register میکنیم و در طول عمر برنامه، یک instance از آن خواهیم داشت:
services.AddSingleton<IEventStoreDbContext, EventStoreDbContext>();
قصد داریم Request هایی را که از نوع Command هستند، همراه با Response آنها داخل EventStore ذخیره کنیم. برای تشخیص Query/Command بودن یک Request ، از نام آنها استفاده خواهیم کرد. همانطور که در قسمتهای قبل گفتیم ، Commandها باید با ذکر "Command" در پایان نامشان همراه باشند.
این یک Convention در برنامه ماست که باید رعایت شود. ( Convention Over Configuration )
مانند Behaviorهای قبلی، یک Behavior جدید را بنام EventLoggerBehavior ایجاد و از IPipelineBehavior ارث بری کرده و EventStoreDbContext خود را به آن Inject میکنیم:
public class EventLoggerBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse> { readonly IEventStoreDbContext _eventStoreDbContext; public EventLoggerBehavior(IEventStoreDbContext eventStoreDbContext) { _eventStoreDbContext = eventStoreDbContext; } public async Task<TResponse> Handle(TRequest request, CancellationToken cancellationToken, RequestHandlerDelegate<TResponse> next) { TResponse response = await next(); string requestName = request.ToString(); // Commands convention if (requestName.EndsWith("Command")) { Type requestType = request.GetType(); string commandName = requestType.Name; var data = new Dictionary<string, object> { { "request", request }, { "response", response } }; string jsonData = JsonConvert.SerializeObject(data); byte[] dataBytes = Encoding.UTF8.GetBytes(jsonData); EventData eventData = new EventData(eventId: Guid.NewGuid(), type: commandName, isJson: true, data: dataBytes, metadata: null); await _eventStoreDbContext.AppendToStreamAsync(eventData); } return response; } }
با استفاده از این Behavior، فقط Request هایی را که Command هستند و State برنامه را تغییر میدهند، داخل EventStore ذخیره میکنیم. اکنون کافیست تا این Behavior را داخل DI Container خود اضافه کنیم :
services.AddScoped(typeof(IPipelineBehavior<,>), typeof(EventLoggerBehavior<,>));
اگر برنامه را اجرا و یکی از Commandها را مانند CreateCustomerCommand، با استفاده از api/Customers <= POST فراخوانی کنید، Request و Response شما با Type آن Command و همراه با DateTime ای که این Request رخ دادهاست، داخل EventStore ذخیره خواهد شد که در Admin Panel مربوط به EventStore، در تب Stream Browser قابل مشاهده است :
Global Address List یا به اختصار GAL و یا همان Microsoft Exchange Global Address Book ، حاوی اطلاعات تمامی کاربران تعریف شده در Exchange server مایکروسافت است و زمانیکه outlook در شبکه به exchange server متصل میشود، کاربران میتوانند با کمک آن لیست اعضاء را مشاهده کرده ، یک یا چند نفر را انتخاب نموده و به آنها ایمیل ارسال کنند (شکل زیر):
نیاز بود تا این لیست تعریف شده در مایکروسافت اکسچنج، با اطلاعات یک دیتابیس مقایسه شوند که آیا این اطلاعات مطابق رکوردهای موجود تعریف شده یا خیر.
بنابراین اولین قدم، استخراج email های موجود در GAL بود (دسترسی به همین برگهی email address که در شکل فوق ملاحظه میکنید از طریق برنامه نویسی) که خلاصه آن تابع زیر است:
جهت استفاده از آن ابتدا باید یک ارجاع به کتابخانه COM ایی به نام Microsoft Outlook Object Library اضافه شود.
using System.Collections.Generic;
using System.Reflection;
using Microsoft.Office.Interop.Outlook;
namespace GAL
{
//add a reference to Microsoft Outlook 12.0 Object Library
class COutLook
{
public struct User
{
public string Name;
public string Email;
}
public static List<User> ExchangeServerEmailAddresses(string userName)
{
List<User> res = new List<User>();
//Create Outlook application
Application outlookApp = new Application();
//Get Mapi NameSpace and Logon
NameSpace ns = outlookApp.GetNamespace("MAPI");
ns.Logon(userName, Missing.Value, false, true);
//Get Global Address List
AddressLists addressLists = ns.AddressLists;
AddressList globalAddressList = addressLists["Global Address List"];
AddressEntries entries = globalAddressList.AddressEntries;
foreach (AddressEntry entry in entries)
{
ExchangeUser user = entry.GetExchangeUser();
if (user != null && user.PrimarySmtpAddress != null && entry.Name != null)
res.Add(new User
{
Name = entry.Name,
Email = user.PrimarySmtpAddress
});
}
ns.Logoff();
// Clean up.
outlookApp = null;
ns = null;
addressLists = null;
globalAddressList = null;
entries = null;
return res;
}
}
}
List<COutLook.User> data = COutLook.ExchangeServerEmailAddresses("nasiri");
foreach (var list in data)
{
//....
}
تنها نکتهی مهم این کد، مهیا نبودن فیلد ایمیل در شیء AdderssEntry است که باید از طریق متد GetExchangeUser آن اقدام شود.
- محیط برنامه نویسی قدرتمند
- هسته اصلی کدهای همه اپلیکیشنها تولید شده شبیه به هم است
- نیازی به یادگیری زبانهای مربوط به هر پلتفرم را ندارید
- کم هزینه و زمان کمتر
- طراحی رابط گرافیکی سریع و منعطف به کمک HTML5 , CSS3
- برنامه نویسی آسان و سریع با javascript , Typescript
- قابلیت اجرا بر روی چندین پلتفرم مختلف(Android,iOS,Widnows Phone )
- قابلیت استفاده از فریمورکهای تحت وب مانند Bootstrap , Angular JS, ...
- قابلیت طراحی پلاگین برای ارتباط با سیستم عامل
- مناسب برای برای برنامههای چت و استفاد از وب سرویسها
- مناسب برای ساخت بازیهای آنلاین و آفلاین با تکنولوژیهای تحت وب
- راحتی کار با آن برای برنامه نویسان تحت وب
- نداشتن ابزار گزارش خطاهای مناسب؛ درنتیجه برطرف کردن خطاها خسته کننده خواهد بود .
- UI, UX اپلیکیشنها باید به نحوی باشد که کاربر حس کند با نرمافزارهای بومی گوشی کار میکند.
- کاهش سرعت اجرایی جزئی نسبت به سایر برنامهها (به دلیل استفاده از WebView)
- عدم دسترسی مستقیم به سیستم عامل و امکانات آن
- Node.js
- Git CLI
- Google Chrome
- Apache Ant
- Oracle Java JDK 7 (حتما نسخه x86 نصب شود)
- Android SDK
- SQLLite For Windows Runtime
- Apple iTunes
- node.js را از لینک مقابل دانلود کنید: اینجا (پیشنهاد میکنیم نسخهی x86 آن را نصب کنید)
- Google Chrome را نصب کنید
- Git Command Line Tools را نصب کنید و توجه کنید که در هنگام نصب، گزینه مربوط به افزودن Git را به مسیر Command Prompt شما، انتخاب کرده باشید.
- Apchage Ant را دانلود و در مسیری از سیستم خودتان قرار دهید.
- Java JDK 7 x86 را از لینک مشخص شده دانلود کنید و سپس عملیات نصب را انجام دهید.
- Android SDK را از آدرس مشحص شده دانلود کنید. پکیچهای مورد نیاز، به این SDK افزوده شده است. بعد از دانلود آن را در مسیری از سیستم خود قرار دهید.
- Apple iTunes و SQLite را دانلود و نصب کنید.
- اگر از ویندوز 7 استفاده میکنید ، WebSocket4Net را از لینک مقابل دانلود کنید ( اینجا ) و سپس فایل net45\Release\WebSocket4Net.dll در مسیر زیر کپی کنید:
%GIT_HOME%\cmd;C:\Program Files (x86)\nodejs\;%JAVA_HOME%\bin;%ANT_HOME%\bin; %ANDROID_HOME%\tools;%ANDROID_HOME%\platform-tools; C:\ProgramData\Oracle\Java\javapath;
نکته تکمیلی
اسمبلیهای نام قوی در برابر دستکاری مقاوم هستند
از آنجائیکه محتویات اسمبلی، هش شده و مقدار هش آن امضا میشود، در نتیجه اگر شخصی به دستکاری اسمبلی اقدام کرده باشد یا اینکه فایل مد نظر آسیب دیده باشد، به راحتی قابل شناسایی است و آن اسمبلی به عنوان اسمبلی صحیح شناسایی نخواهد شد و نمیگذارد در GAC ثبت شود.
موقعیکه برنامه نیاز داشته باشد به اسمبلی نام قوی بایند یا متصل شود، از 4 ویژگی گفته شدهی در قسمت قبلی استفاده میکند تا آن را در GAC بیابد. اگر اسمبلی درخواستی موجود باشد، زیر دایرکتوری آن برگشت داده خواهد شد؛ ولی اگر آن را نیابد، ابتدا در داخل دایرکتوری برنامه و سپس در مسیرهایی که در فایل پیکربندی ذکر شدهاند، به دنبال آن خواهد گشت و در نهایت اگر برنامه توسط فایل MSI نصب شده باشد، محلهای توزیع را از طریق آن جویا خواهد شد و اگر باز به نتیجهای نرسد، اتصال ناموفق گزارش شده و خطای زیر را ایجاد خواهد کرد:
System.IO.FileNotFoundException
System.IO.FileLoadExceptio
- جلوگیری از دستکاری و حفظ امنیت آن
- این اسمبلی تنها یکبار از حافظهی فیزیکی استفاده میکند؛ برعکس توزیع خصوصی که برای هر برنامه باید یک فضای دیسکی داشته باشد.
- توزیع سادهتر برای نسخههای آینده فراهم میشود که بعدا در مورد آن توضیح میدهیم.
سیاستهای انتخاب اسمبلی توسط GAC
موقعی که GAC میخواهد یک اسمبلی را برای برنامه شما بازگرداند، از روی خصوصیاتی چون نام اسمبلی، ورژن، فرهنگ، توکن کلید عمومی و در نهایت بسته به معماری ماشین، یک اسمبلی را برمیگرداند. در صورتیکه اسمبلی خاصی را برای ماشین مورد نظر پیدا نکند، از اسمبلی یک ماشین دیگر استفاده خواهد کرد. در واقع این انتخاب، یک سیاست پیش فرض است که میتواند از طریق ناشر یا مدیر سیستم تغییر پیدا کند و رونویسی شود.
کنترل مدیریت پیشرفته (پیکربندی)
در قسمت بیستم با ساخت فایل پیکریندی و نحوه تنظیم کردن اسکن اسمبلیها در CLR آشنا شدیم. این بار قصد داریم در مورد المانهای دیگر این فایل پیکریندی صحبت کنیم. فایل پیکربندی زیر را بررسی میکنیم:
<?xml version="1.0"?> <configuration> <runtime> <assemblyBinding xmlns="urn:schemasmicrosoftcom:asm.v1"> <probing privatePath="AuxFiles;bin\subdir" /> <dependentAssembly> <assemblyIdentity name="SomeClassLibrary" publicKeyToken="32ab4ba45e0a69a1" culture="neutral"/> <bindingRedirect oldVersion="1.0.0.0" newVersion="2.0.0.0" /> <codeBase version="2.0.0.0" href="http://www.Wintellect.com/SomeClassLibrary.dll" /> </dependentAssembly> <dependentAssembly> <assemblyIdentity name="TypeLib" publicKeyToken="1f2e74e897abbcfe" culture="neutral"/> <bindingRedirect oldVersion="3.0.0.03.5.0.0" newVersion="4.0.0.0" /> <publisherPolicy apply="no" /> </dependentAssembly> </assemblyBinding> </runtime> </configuration>
Probing | در
قسمت بیستم گفتیم که در این المان، مکانهایی را که CLR برای پیدا کردن
اسمبلیهای با نام ضعیف، باید اسکن کند، وارد میکنیم که هر مسیر با , از هم جدا شدهاست. برای اسمبلیهای با نام قوی CLR باید داخل GAC را نگاه
کند و در URL هایی که از طریق المان CodeBase مشخص کردهایم. اگر المان
CodeBase مشخص نشود، CLR برای پیدا کردن اسمبلیهای با نام قوی، داخل
دایرکتوریهای این المان را اسکن خواهد کرد. |
Dependent Assembly اول | در
اولین فرزند این المان، Assembly Identity یک اسمبلی با مشخصاتی مثل
Culture و توکن عمومی معرفی میشود و در BindingRedirect به CLR اطلاع
میدهد موقعی که به دنبال نسخه یک این اسمبلی است، نسخهی دو آن را برای
استفاده جایگزین کند. |
Code Base | این
المان میگوید که وقتی CLR سعی در پیدا کردن نسخهی 2 اسمبلی را دارد، آن را
از طریق آدرس مورد نظر پیدا کند. این المان میتواند برای اسمبلیهای با نام
ضعیف هم کار کند. |
Dependent Assembly دوم | این مورد هم همانند سابق است با این تفاوت که گسترهی نسخه 3 تا 3.5 را به نسخهی 4 تغییر میدهد. |
Publisher Policy | اگر
سازمان تولید کننده این اسمبلی فایلی برای تعیین Policy به همراه اسمبلی
ارائه کرده باشد، این المان باعث میشود این فایل ندیده گرفته شود (در مورد
فایل Policy در آینده صحبت میکنیم). |
با وجود این حالت اگر فرض کنیم مدیر یک سیستم متوجه شود که اسمبلی برنامه دچار مشکل شده است و با ناشر تماس بگیرد و ناشر نسخهی جدیدی از آن اسمبلی را در اختیار او بگذارد، مدیر سیستم میتواند به راحتی از طریق فایل پیکربندی، CLR را به استفادهی از اسمبلی جدید به جای اسمبلی قدیمی هدایت کند.
نکته : اگر مدیر بخواهد تمام برنامههای موجود از این اسمبلی جدید استفاده کنند باید فایل machine.config را ویرایش کند.
ASP.NET Web API فریم ورکی برای ساختن APIهای وب بر روی فریم ورک دات نت است. در این مقاله با استفاده از این فریم ورک، API وبی خواهیم ساخت که لیستی از محصولات را بر میگرداند. صفحه وب کلاینت، با استفاده از jQuery نتایج را نمایش خواهد داد.
یک پروژه Web API بسازید
در ویژوال استودیو 2013 پروژه جدیدی از نوع ASP.NET Web Application بسازید و نام آن را "ProductsApp" انتخاب کنید.
در دیالوگ New ASP.NET Project قالب Empty را انتخاب کنید و در قسمت "Add folders and core references for" گزینه Web API را انتخاب نمایید.
می توانید از قالب Web API هم استفاده کنید. این قالب با استفاده از ASP.NET MVC صفحات راهنمای API را خواهد ساخت. در این مقاله از قالب Empty استفاده میکنیم تا تمرکز اصلی، روی خود فریم ورک Web API باشد. بطور کلی برای استفاده از این فریم ورک لازم نیست با ASP.NET MVC آشنایی داشته باشید.
افزودن یک مدل
یک مدل (model) آبجکتی است که داده اپلیکیشن شما را معرفی میکند. ASP.NET Web API میتواند بصورت خودکار مدل شما را به JSON, XML و برخی فرمتهای دیگر مرتب (serialize) کند، و سپس داده مرتب شده را در بدنه پیام HTTP Response بنویسد. تا وقتی که یک کلاینت بتواند فرمت مرتب سازی دادهها را بخواند، میتواند آبجکت شما را deserialize کند. اکثر کلاینتها میتوانند XML یا JSON را تفسیر کنند. بعلاوه کلاینتها میتوانند فرمت مورد نظرشان را با تنظیم Accept header در پیام HTTP Request مشخص کنند.
بگذارید تا با ساختن مدلی ساده که یک محصول (product) را معرفی میکند شروع کنیم.
کلاس جدیدی در پوشه Models ایجاد کنید.
نام کلاس را به "Product" تغییر دهید، و خواص زیر را به آن اضافه کنید.
namespace ProductsApp.Models { public class Product { public int Id { get; set; } public string Name { get; set; } public string Category { get; set; } public decimal Price { get; set; } } }
افزودن یک کنترلر
در Web API کنترلرها آبجکت هایی هستند که درخواستهای HTTP را مدیریت کرده و آنها را به اکشن متدها نگاشت میکنند. ما کنترلری خواهیم ساخت که میتواند لیستی از محصولات، یا محصولی بخصوص را بر اساس شناسه برگرداند. اگر از ASP.NET MVC استفاده کرده اید، با کنترلرها آشنا هستید. کنترلرهای Web API مشابه کنترلرهای MVC هستند، با این تفاوت که بجای ارث بری از کلاس Controller از کلاس ApiController مشتق میشوند.
کنترلر جدیدی در پوشه Controllers ایجاد کنید.
در دیالوگ Add Scaffold گزینه Web API Controller - Empty را انتخاب کرده و روی Add کلیک کنید.
در دیالوگ Add Controller نام کنترلر را به "ProductsController" تغییر دهید و روی Add کلیک کنید.
توجه کنید که ملزم به ساختن کنترلرهای خود در پوشه Controllers نیستید، و این روش صرفا قراردادی برای مرتب نگاه داشتن ساختار پروژهها است. کنترلر ساخته شده را باز کنید و کد زیر را به آن اضافه نمایید.
using ProductsApp.Models; using System; using System.Collections.Generic; using System.Linq; using System.Net; using System.Web.Http; namespace ProductsApp.Controllers { public class ProductsController : ApiController { Product[] products = new Product[] { new Product { Id = 1, Name = "Tomato Soup", Category = "Groceries", Price = 1 }, new Product { Id = 2, Name = "Yo-yo", Category = "Toys", Price = 3.75M }, new Product { Id = 3, Name = "Hammer", Category = "Hardware", Price = 16.99M } }; public IEnumerable<Product> GetAllProducts() { return products; } public IHttpActionResult GetProduct(int id) { var product = products.FirstOrDefault((p) => p.Id == id); if (product == null) { return NotFound(); } return Ok(product); } }
کنترلر ما دو متد برای دریافت محصولات تعریف میکند:
- متد GetAllProducts لیست تمام محصولات را در قالب یک <IEnumerable<Product بر میگرداند.
- متد GetProductById سعی میکند محصولی را بر اساس شناسه تعیین شده پیدا کند.
همین! حالا یک Web API ساده دارید. هر یک از متدهای این کنترلر، به یک یا چند URI پاسخ میدهند:
URI | Controller Method |
api/products/ | GetAllProducts |
api/products/id/ | GetProductById |
برای اطلاعات بیشتر درباره نحوه نگاشت درخواستهای HTTP به اکشن متدها توسط Web API به این لینک مراجعه کنید.
فراخوانی Web API با جاوا اسکریپت و jQuery
در این قسمت یک صفحه HTML خواهیم ساخت که با استفاده از AJAX متدهای Web API را فراخوانی میکند. برای ارسال درخواستهای آژاکسی و بروز رسانی صفحه بمنظور نمایش نتایج دریافتی از jQuery استفاده میکنیم.
در پنجره Solution Explorer روی نام پروژه کلیک راست کرده و گزینه Add, New Item را انتخاب کنید.
در دیالوگ Add New Item قالب HTML Page را انتخاب کنید و نام فایل را به "index.html" تغییر دهید.
حال محتوای این فایل را با لیست زیر جایگزین کنید.
<!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <title>Product App</title> </head> <body> <div> <h2>All Products</h2> <ul id="products" /> </div> <div> <h2>Search by ID</h2> <input type="text" id="prodId" size="5" /> <input type="button" value="Search" onclick="find();" /> <p id="product" /> </div> <script src="http://ajax.aspnetcdn.com/ajax/jQuery/jquery-2.0.3.min.js"></script> <script> var uri = 'api/products'; $(document).ready(function () { // Send an AJAX request $.getJSON(uri) .done(function (data) { // On success, 'data' contains a list of products. $.each(data, function (key, item) { // Add a list item for the product. $('<li>', { text: formatItem(item) }).appendTo($('#products')); }); }); }); function formatItem(item) { return item.Name + ': $' + item.Price; } function find() { var id = $('#prodId').val(); $.getJSON(uri + '/' + id) .done(function (data) { $('#product').text(formatItem(data)); }) .fail(function (jqXHR, textStatus, err) { $('#product').text('Error: ' + err); }); } </script> </body> </html>
گرفتن لیستی از محصولات
برای گرفتن لیستی از محصولات، یک درخواست HTTP GET به آدرس "api/products/" ارسال کنید.
تابع getJSON یک درخواست آژاکسی ارسال میکند. پاسخ دریافتی هم آرایه ای از آبجکتهای JSON خواهد بود. تابع done در صورت موفقیت آمیز بودن درخواست، اجرا میشود. که در این صورت ما DOM را با اطلاعات محصولات بروز رسانی میکنیم.
$(document).ready(function () { // Send an AJAX request $.getJSON(apiUrl) .done(function (data) { // On success, 'data' contains a list of products. $.each(data, function (key, item) { // Add a list item for the product. $('<li>', { text: formatItem(item) }).appendTo($('#products')); }); }); });
گرفتن محصولی مشخص
برای گرفتن یک محصول توسط شناسه (ID) آن کافی است یک درخواست HTTP GET به آدرس "api/products/id/" ارسال کنید.
function find() { var id = $('#prodId').val(); $.getJSON(apiUrl + '/' + id) .done(function (data) { $('#product').text(formatItem(data)); }) .fail(function (jqXHR, textStatus, err) { $('#product').text('Error: ' + err); }); }
اجرای اپلیکیشن
اپلیکیشن را با F5 اجرا کنید. صفحه وب باز شده باید چیزی مشابه تصویر زیر باشد.
برای گرفتن محصولی مشخص، شناسه آن را وارد کنید و روی Search کلیک کنید.
اگر شناسه نامعتبری وارد کنید، سرور یک خطای HTTP بر میگرداند.
استفاده از F12 برای مشاهده درخواستها و پاسخ ها
هنگام کار با سرویسهای HTTP، مشاهدهی درخواستهای ارسال شده و پاسخهای دریافتی بسیار مفید است. برای اینکار میتوانید از ابزار توسعه دهندگان وب استفاده کنید، که اکثر مرورگرهای مدرن، پیاده سازی خودشان را دارند. در اینترنت اکسپلورر میتوانید با F12 به این ابزار دسترسی پیدا کنید. به برگه Network بروید و روی Start Capturing کلیک کنید. حالا صفحه وب را مجددا بارگذاری (reload) کنید. در این مرحله اینترنت اکسپلورر ترافیک HTTP بین مرورگر و سرور را تسخیر میکند. میتوانید تمام ترافیک HTTP روی صفحه جاری را مشاهده کنید.
به دنبال آدرس نسبی "api/products/" بگردید و آن را انتخاب کنید. سپس روی Go to detailed view کلیک کنید تا جزئیات ترافیک را مشاهده کنید. در نمای جزئیات، میتوانید headerها و بدنه درخواستها و پاسخها را ببینید. مثلا اگر روی برگه Request headers کلیک کنید، خواهید دید که اپلیکیشن ما در Accept header دادهها را با فرمت "application/json" درخواست کرده است.
اگر روی برگه Response body کلیک کنید، میتوانید ببینید چگونه لیست محصولات با فرمت JSON سریال شده است. همانطور که گفته شده مرورگرهای دیگر هم قابلیتهای مشابهی دارند. یک ابزار مفید دیگر Fiddler است. با استفاده از این ابزار میتوانید تمام ترافیک HTTP خود را مانیتور کرده، و همچنین درخواستهای جدیدی بسازید که این امر کنترل کاملی روی HTTP headers به شما میدهد.
قدمهای بعدی
قابلیتهای نسخههای مختلف Sql Server Express 2008
نسخه / قابلیت | Database Engine | Management Studio Basic | Full-Text Search | Reporting Services |
Management Studio Basic | X | |||
Runtime Only | X | |||
with Tools | X | X | ||
with Advanced Services | X | X | X | X |
(SQL Server 2008 Express (Runtime Only
SQL Server 2008 Express with Tools
SQL Server 2008 Express with Advanced Services
دو حالت برای نصب SQL Server وجود دارد:
نصب SQL Server Express 2008 از طریق Command Prompt
Setup.exe /q /Action=Install /Hideconsole /Features=SQL,Tools /InstanceName=SQLExpress /SQLSYSADMINACCOUNTS="Builtin\Administrators" /SQLSVCACCOUNT="<DomainName\UserName>" /SQLSVCPASSWORD="<StrongPassword>
باتوجه به سیاستها نصب میتوانید از پارامترها دیگری نیز استفاده کنید. بعنوان مثال پارامترهای زیر برای نصب روی سیستمی که نام کاربری و پسورد انرا نداریم مناسب است:
setup.exe /q /Action=Install /Features=SQL /InstanceName=SQLExpress /SECURITYMODE=SQL /SAPWD="1234567" /SQLSYSADMINACCOUNTS="Builtin\Administrators" /SQLSVCACCOUNT="NT AUTHORITY\SYSTEM" /SQLSVCSTARTUPTYPE="Automatic" /TCPENABLED=1
Setup.exe /q /Hideconsole /ACTION=upgrade /INSTANCENAME=SQLExpress
برای مشاهده دیگر پارامترها به مستندات MSDN مراجعه کنید. همچنین میتوان نصب از طریق فایل Configuration را نیز انجام داد.
قسمت اول
قسمت دوم
در تکمیل قسمتهای فوق بنده میخوام مثالی رو در این رابطه براتون بذارم، هدف از ارائه این مثال اتوماتیک سازی یک فرآیند روتین میباشد، به این صورت که در جایی که بنده مشغول به کار هستم یک سری لایسنس آنتی ویروس برای کلاینتها در یک شبکه با مقیاس متوسط تهیه گردیده است، حال یک نسخه رایگان نیز برای کاربرانی که قصد دارند آنتی ویروس را برای سیستم شخصی خود نصب کنند نیز موجود میباشد که نیاز به آپدیت دارد معمولا آپدیتها هر چند روز یکبار یا هر هفته در دو نسخه 64 و 32 بیتی ارائه میشوند، روال معمول برای دریافت آپدیت مراجعه به سایت و دانلود نسخههای مربوطه میباشد.
حال توسط کتابخانه قدرتمند Quartz.NET این فرآیند روتین را به صورت اتوماتیک میخواهیم انجام دهیم، استفاده از کتابخانه ذکر شده سخت نیست همانطور که در دو مطلب قبلی مرتبط ذکر گردیده، تنها پیاده سازی چندین اینترفیس است و بس.
namespace SymantecUpdateDownloader { using System; using System.IO; using Quartz; using Quartz.Impl; using System.Globalization; public class TestJob : IJob { public void Execute(IJobExecutionContext context) { new Download().Scraping(); } } public interface ISchedule { void Run(); } public class TestSchedule : ISchedule { public void Run() { DateTimeOffset startTime = DateBuilder.FutureDate(2, IntervalUnit.Second); IJobDetail job = JobBuilder.Create<HelloJob>() .WithIdentity("job1") .Build(); ITrigger trigger = TriggerBuilder.Create() .WithIdentity("trigger1") .StartAt(startTime) .WithDailyTimeIntervalSchedule(x => x.OnEveryDay().StartingDailyAt(new TimeOfDay(7, 0)).WithRepeatCount(0)) .Build(); ISchedulerFactory sf = new StdSchedulerFactory(); IScheduler sc = sf.GetScheduler(); sc.ScheduleJob(job, trigger); sc.Start(); } } }
مورد بعدی عملیات دانلود فایل میباشد که در ادامه مشاهده خواهید کرد، صفحه ایی که لینک فایلهای دانلود را ارائه داده است دو نسخه مد نظر ما را در ابتدا لیست کرده است و با استفاده از web scrapingمی توانیم موارد تعیین شده را استخراج کنیم برای این منظور از کتابخانه htmlagilitypack استفاده میکنیم، تطبیق دو مورد(لینک) اول جهت دریافت نسخههای 32 و 64 بیتی به کمک Regular Expression میسر است و همانطور که در شکل زیر مشاهده میکنید از سمت چپ تاریخ به صورت 8 رقم، سه رقم قسمت دوم و ارقام و حروف قسمت سوم است به اضافه پسوند فایل مشخص است :
public class Download { static WebClient wc = new WebClient(); static ManualResetEvent handle = new ManualResetEvent(true); private DateTime myDate = new DateTime(); public void Scraping() { using (WebClient client = new WebClient()) { client.Encoding = System.Text.Encoding.UTF8; var doc = new HtmlAgilityPack.HtmlDocument(); ArrayList result = new ArrayList(); doc.LoadHtml(client.DownloadString("https://www.symantec.com/security_response/definitions/download/detail.jsp?gid=savce")); var tasks = new List<Task>(); foreach (var href in doc.DocumentNode.Descendants("a").Select(x => x.Attributes["href"])) { if (href == null) continue; string s = href.Value; Match m = Regex.Match(s, @"http://definitions.symantec.com/defs/(\d{8}-\d{3}-v5i(32|64)\.exe)"); if (m.Success) { Match date = Regex.Match(m.Value, @"(\d{4})(\d{2})(\d{2})"); Match filename = Regex.Match(m.Value, @"\d{8}-\d{3}-v5i(32|64)\.exe"); int year = Int32.Parse(date.Groups[0].Value); int month = Int32.Parse(date.Groups[1].Value); int day = Int32.Parse(date.Groups[3].Value); myDate = new DateTime( Int32.Parse(date.Groups[1].Value), Int32.Parse(date.Groups[2].Value), Int32.Parse(date.Groups[3].Value)); if (myDate == DateTime.Today) { tasks.Add(DownloadUpdate(m.Value, filename.Value)); } else { MessageBox.Show("امروز آپدیت موجود نیست"); } } } DownloadTask = Task.WhenAll(tasks); } } private static Task DownloadTask; private Task DownloadUpdate(string url, string fileName) { var wc = new WebClient(); return wc.DownloadFileTaskAsync(new Uri(url), @"\\10.1.0.15\SymantecUpdate\\" + fileName); } }
ابتدا توسط متد LoadHtml خط 14 صفحه مورد نظر که حاوی لینکها میباشد رو Load میکنیم، سپس توسط یک حلقه foreach خط 16 مقدار خصوصیت href تمام لینکهای موجود در صفحه را استخراج میکنیم مثلا مقدار خصوصیت href در لینکها به صورت زیر میباشد :
http://definitions.symantec.com/defs/20130622-007-v5i32.exe
http://definitions.symantec.com/defs/20130622-007-v5i64.exe
همانطور که مشخص است در دو مورد فوق تنها نام فایل متفاوت میباشد، همانطور که بحث شد برای نام فایلها هم میتوانیم یک Pattern را به صورت زیر داشته باشیم :
(\d{8}-\d{3}-v5i(32|64)\.exe)
int year = Int32.Parse(date.Groups[0].Value); int month = Int32.Parse(date.Groups[1].Value); int day = Int32.Parse(date.Groups[3].Value);
البته لازم به ذکر است که کدهای فوق مسلما نیاز یه Refactoring دارند منتها هدف از ارائه این مثال آشنایی بیشتر با کتابخانههای فوق میباشد.
نکته آخر اینکه برنامه فوق به حالتهای مختلفی میتواند اجرا گردد مثل یک برنامه وب یا یک سرویس ویندوزی و ... ، بهترین حالت یک سرویس ویندوز میباشد، ولی در حالت خام در حال حاضر یک ویندوز اپلیکیشن ساده میباشد که بر روی سرور RUN شده است که در آینده به صورت یک سرویس ویندوز ارائه خواهد شد.
EF Code First #12
- ذکر یک متد در اینترفیس (یک قرار داد) به جهت امکان استفاده از آن است. شما نهایتا با متدهای تعریف شده در طراحی IUnitOfWork در لایه سرویس قرار است کار کنید نه مستقیما با کلاس مشتق شده از DbContext.
زمانیکه مینویسید:
public class MyContext : DbContext, IUnitOfWork
MyContext به صورت خودکار امکان دسترسی به متد SaveChanges موجود در DbContext را پیدا میکند. کتابخانه StructureMap میتونه زمانیکه نیازی به یک وهله پیاده ساز IUnitOfWork بود، از MyContext استفاده کنه. همچنین چون الان SaveChanges با امضایی که در اینترفیس IUnitOfWork وجود دارد در کلاس MyContext هم قابل دسترسی است، نیازی به پیاده سازی مجدد آن نیست.