ASP.NET MVC #10
- هدف من تکمیل بحث بود. آشنایی با انواع روشهای ممکن.
ضمن اینکه من چند سال قبل به کمک روش Request.Form که توضیح دادم، یک form generator برای ASP.NET Web forms نوشتم. زمانیکه کنترلهای وب فرمها به صورت پویا به صفحه اضافه بشن، دیگه eventها جهت دریافت مقادیر اونها معنا نخواهند داشت (با توجه به اینکه کل صفحه رو بخواهیم پویا تولید کنیم و برنامه چند صد فرم پویا داشته باشد). در اینجا از همین روش Request.Form برای دریافت مقادیر کنترلهای پویای اضافه شده به صفحه استفاده کردم.
ساخت و ایجاد درخواستهای Postman به کمک خروجی OpenAPI
در اینجا از همان برنامهای که در سری «مستند سازی ASP.NET Core 2x API توسط OpenAPI Swagger» بررسی کردیم، استفاده خواهیم کرد. بنابراین، این برنامه از پیش تنظیم شدهاست و هم اکنون به همراه یک تولید کنندهی OpenAPI Specification نیز میباشد. آنرا اجرا کنید تا بتوان به OpenAPI Specification تولیدی آن در آدرس زیر دسترسی یافت:
https://localhost:5001/swagger/LibraryOpenAPISpecification/swagger.json
در برگهی Import from link آن، همان URL فوق را که به خروجی OpenAPI Spec اشاره میکند، وارد کنید. اکنون با کلیک بر روی دکمهی Import، یک مجموعهی جدید، به نام Library API، به لیست مجموعههای Postman، اضافه میشود:
Postman تمام این اطلاعات را به صورت خودکار از OpenAPI Spec استخراج کردهاست. تمام نامها نیز بر اساس توضیحاتی که برای متدها نوشتهایم، انتخاب شدهاند.
ارسال اولین درخواست به Web API
در اینجا برای نمونه اگر درخواست «Get list of authors» را انتخاب کنیم، یک چنین خروجی ظاهر میشود:
همانطور که مشاهده میکنید، متغیر {{baseUrl}} را جهت تنظیم آدرس پایهی Web API انتخاب کردهاست. این نکته در مطلب «قسمت پنجم - انواع متغیرهای قابل تعریف در Postman» بیشتر بحث شدهاست. هدف از تعریف متغیر {{baseUrl}} به این شکل در اینجا، امکان تعریف آن به صورت یک متغیر محیطی است تا بتوان آنرا به سادگی بر اساس محیطهای مختلفی که تعریف و انتخاب میکنیم، تغییر داد؛ بدون اینکه نیازی باشد اصل درخواستهای تعریف شده، تغییری کنند. بنابراین در ادامه نیاز است یک محیط جدید را تعریف کنیم.
برای تعریف یک محیط جدید میتوان بر روی دکمهای با آیکن چشم، در بالای سمت راست صفحه و کلیک بر روی گزینهی Add آن، یک محیط جدید را ایجاد کرد:
در صفحهی باز شده ابتدا باید نامی را برای این محیط جدید انتخاب کرد و سپس میتوان key/valueهایی را مخصوص این محیط، تعریف نمود:
ابتدا یک نام دلخواه وارد شدهاست و سپس متغیر محیطی baseUrl را با مقدار اولیهی https://localhost:5001 تنظیم کردهایم. پس از آن با کلیک بر روی Add پایین این صفحه، کار تعریف این محیط جدید به پایان میرسد.
مرحلهی بعد، انتخاب این محیط تعریف شده، به عنوان محیط کاری جاری است:
پس از این انتخاب، اگر اشارهگر ماوس را به متغیر baseUrl نزدیک کنیم، میتوان مقدار تنظیم شدهی آنرا مشاهده کرد:
اکنون اگر بر روی دکمهی send این درخواست کلیک کنیم، چنین خروجی ظاهر میشود:
علت آنرا میتوان در برگهی Authorization درخواست جاری مشاهده کرد:
همانطور که در مطلب «قسمت ششم - یک مثال تکمیلی: تبدیل رابط کاربری مثال JWT به یک مجموعهی Postman» نیز مشاهده کردیم، برای تعریف هدرهای Authorization یا میتوان به برگهی هدرهای درخواست جاری مراجعه کرد و این هدرها را دستی تولید کرد و یا میتوان با استفاده از برگهی Authorization آن، کار تعریف این هدرها را ساده نمود. برای مثال در اینجا Postman بر اساس خروجی OpenAPI، دقیقا تشخیص دادهاست که این Web API از Basic authentication استفاده میکند. به همین جهت فیلدهای ورود نام کاربری و کلمهی عبور را علاوه بر نوع اعتبارسنجی از پیش انتخاب شده، تدارک دیدهاست.
برای اینکه این مقادیر را نیز تبدیل به متغیرهای محیطی کنیم، برای ویرایش اطلاعات منتسب به محیط جاری، ابتدا باید آنرا از dropdown محیطهای بالای صفحه انتخاب کرد. اکنون با کلیک بر روی دکمهای با آیکن چشم، در بالای سمت راست صفحه، لینک ویرایش این محیط انتخاب شده ظاهر میشود. با کلیک بر روی آن، میتوان دو متغیر محیطی جدید را تعریف کرد:
پس از تعریف متغیرهای محیطی {{username}} و {{password}}، آنها را در قسمت Authorization درخواست جاری استفاده میکنیم:
اینبار اگر مجددا بر روی دکمهی Send کلیک کنیم، خروجی ذیل حاصل خواهد شد:
چند نکته کاربردی درباره Entity Framework
پیشنیاز
- نقشه راه «آزمون واحد در دات نت»
- مطلب «طراحی و پیاده سازی ServiceLayer به همراه خودکارسازی Business Validationها»
در این مطلب قصد داریم تست ServiceLayer را به جای تست درون حافظهای که با ابزارهای Mocking در قالب Unit Testing انجام میگیرد، به کمک یک دیتابیس واقعی سبک وزن در قالب Integration Testing انجام دهیم.
قدم اول
یک پروژه تست را ایجاد کنید؛ بهتر است برای نظم دهی به ساختار Solution، پروژههای تست را در پوشه ای به نام Tests نگهداری کنید.
قدم دوم
بستههای نیوگت زیر را نصب کنید:
PM> install-package NUnit PM> install-package Shouldly PM> install-package EntityFramework PM> install-package FakeHttpContext
قدم سوم
نسخه دیتابیس انتخابی برای تست خودکار، LocalDB می باشد. لازم است در ابتدای اجرای تستها دیتابیس مربوط به Integration Test ایجاد شده و بعد از اتمام نیز دیتابیس مورد نظر حذف شود؛ برای این منظور از کلاس TestSetup استفاده خواهیم کرد.
[SetUpFixture] public class TestSetup { [OneTimeSetUp] public void SetUpDatabase() { DestroyDatabase(); CreateDatabase(); } [OneTimeTearDown] public void TearDownDatabase() { DestroyDatabase(); } //... }
با توجه به اینکه کلاس TestSetup با [SetUpFixture] تزئین شده است، Nunit قبل از اجرای تستها سراغ این کلاس آمده و متد SetUpDatebase را به دلیل تزئین شدن با [OneTimeSetUp]، قبل از اجرای تستها و متد TearDownDatabase را بدلیل تزئین شدن با [OneTimeTearDown] بعد از اجرای تمام تستها، اجرا خواهد کرد.
متد CreateDatabase
private static void CreateDatabase() { ExecuteSqlCommand(Master, string.Format(SqlResource.DatabaseScript, FileName)); //Use T-Sql Scripts For Create Database //ExecuteSqlCommand(MyAppTest, SqlResources.V1_0_0); var migration = new MigrateDatabaseToLatestVersion<ApplicationDbContext, DataLayer.Migrations.Configuration>(); migration.InitializeDatabase(new ApplicationDbContext()); } private static SqlConnectionStringBuilder Master => new SqlConnectionStringBuilder { DataSource = @"(LocalDB)\MSSQLLocalDB", InitialCatalog = "master", IntegratedSecurity = true }; private static string FileName => Path.Combine( Path.GetDirectoryName( Assembly.GetExecutingAssembly().Location), "MyAppTest.mdf");
برای مدیریت محل ذخیره سازی فایلهای دیتابیس، ابتدا دستورات ایجاد «دیتابیس تست» را برروی دیتابیس master اجرا میکنیم و در ادامه برای ساخت جداول از مکانیزم Migration خود EF استفاده شده است.
لازم است رشته اتصال به این دیتابیس ایجاد شده را در فایل App.config پروژه تست قرار دهید:
<connectionStrings> <add name="DefaultConnection" providerName="System.Data.SqlClient" connectionString="Data Source=(LocalDB)\MSSQLLocalDb;Initial Catalog=MyAppTest;Integrated Security=True;" /> </connectionStrings>
متد DestroyDatabase
private static void DestroyDatabase() { var fileNames = ExecuteSqlQuery(Master, SqlResource.SelecDatabaseFileNames, row => (string)row["physical_name"]); if (!fileNames.Any()) return; ExecuteSqlCommand(Master, SqlResource.DetachDatabase); fileNames.ForEach(File.Delete); }
در این متد ابتدا آدرس فایلهای مرتبط با «دیتابیس تست» واکشی شده و در ادامه دستورات Detach دیتابیس انجام شده و فایلهای مرتبط حذف خواهند شد. فایلهای دیتابیس در مسیری شبیه به آدرس نشان داده شدهی در شکل زیر ذخیره خواهند شد.
قدم چهارم
برای جلوگیری از تداخل بین تستها لازم است تک تک تستها از هم ایزوله باشند؛ یکی از راه حلهای موجود، استفاده از تراکنشها میباشد. برای این منظور امکان AutoRollback را به صورت خودکار به متدهای تست با استفاده از Attribute زیر اعمال خواهیم کرد:
public class AutoRollbackAttribute : Attribute, ITestAction { private TransactionScope _scope; public void BeforeTest(ITest test) { _scope = new TransactionScope(TransactionScopeOption.RequiresNew,new TransactionOptions {IsolationLevel = IsolationLevel.Snapshot}); } public void AfterTest(ITest test) { _scope?.Dispose(); _scope = null; } public ActionTargets Targets => ActionTargets.Test; }
متدهای BeforTest و AfterTest به ترتیب قبل و بعد از اجرای متدهای تست تزئین شده با این Attribute اجرا خواهند شد.
در مواقعی هم که به HttpConext نیاز دارید، میتوانید از کتابخانه FakeHttpContext بهره ببرید. برای این مورد هم میتوان Attributeای را به مانند AutoRollback در نظر گرفت.
public class HttpContextAttribute:Attribute,ITestAction { private FakeHttpContext.FakeHttpContext _httpContext; public void BeforeTest(ITest test) { _httpContext = new FakeHttpContext.FakeHttpContext(); } public void AfterTest(ITest test) { _httpContext?.Dispose(); _httpContext = null; } public ActionTargets Targets => ActionTargets.Test; }
کاری که FakeHttpContext انجام میدهد، مقدار دهی HttpContext.Current با یک پیاده سازی ساختگی میباشد.
قدم پنجم
به عنوان مثال اگر بخواهیم برای سرویس «گروه کاربری»، Integration Test بنویسیم، به شکل زیر عمل خواهیم کرد:
namespace MyApp.IntegrationTests.ServiceLayer { [TestFixture] [AutoRollback] [HttpContext] public class RoleServiceTests { private IRoleApplicationService _roleService; [SetUp] public void Init() { } [TearDown] public void Clean() { } [OneTimeSetUp] public void SetUp() { _roleService = IoC.Resolve<IRoleApplicationService>(); using (var uow = IoC.Resolve<IUnitOfWork>()) { RoleInitialDataBuilder.Build(uow); } } [OneTimeTearDown] public void TearDown() { } [Test] [TestCase("Role1")] public void Should_Create_New_Role(string role) { var viewModel = new RoleCreateViewModel { Name = role }; _roleService.Create(viewModel); using (var context = IoC.Resolve<IUnitOfWork>()) { var user = context.Set<Role>().FirstOrDefault(a => a.Name == role); user.ShouldNotBeNull(); } } [Test] public void Should_Not_Create_New_Role_With_Admin_Name() { var viewModel = new RoleCreateViewModel { Name = "Admin" }; Assert.Throws<DbUpdateException>(() => _roleService.Create(viewModel)); } [Test] public void Should_AdminRole_Exists() { using (var context = IoC.Resolve<IUnitOfWork>()) { var user = context.Set<Role>().FirstOrDefault(a => a.Name == "Admin"); user.ShouldNotBeNull(); } } [Test] public void Should_Not_Create_New_Role_Without_Name() { Assert.Throws<ValidationException>(() => _roleService.Create(new RoleCreateViewModel { Name = null })); } } }
با این خروجی:
معرفی ELMAH
تمام هاستها برای ارسال ایمیل نیاز به smtp authentication دارند. برای این منظور باید یوزر نیم و پسورد داده شده به شما را در web.config برنامه اضافه کنید که در این مقاله توضیح داده شده است:
http://www.4guysfromrolla.com/articles/072606-1.aspx
modelBuilder.Entity<IdentityUser>().ToTable("MyUserRoles");
OpenID چیست؟
چون Active Directory با Domain تنظیم میشه
اما این حالت احراز هویت روی فرم تنظیم میشه
برای SSO مطلب توی همین سایت هست در واقع توی اون روش ما با یک اکانت که توی یک سایت داریم از سایتهای دیگه روی همون سرویس استفاده میکنیم و میشه گفت حالت اختصاصی داره
اما توی این حالت شما با هر سایتی که این قابلیت داشته باشه میتونی کار کنی