ASP.NET MVC #10
- هدف من تکمیل بحث بود. آشنایی با انواع روشهای ممکن.
ضمن اینکه من چند سال قبل به کمک روش Request.Form که توضیح دادم، یک form generator برای ASP.NET Web forms نوشتم. زمانیکه کنترلهای وب فرمها به صورت پویا به صفحه اضافه بشن، دیگه eventها جهت دریافت مقادیر اونها معنا نخواهند داشت (با توجه به اینکه کل صفحه رو بخواهیم پویا تولید کنیم و برنامه چند صد فرم پویا داشته باشد). در اینجا از همین روش Request.Form برای دریافت مقادیر کنترلهای پویای اضافه شده به صفحه استفاده کردم.
- جذب
- انتخاب یا گزینش
مرحله جذب:
این مرحله مربوط است به آگهی استخدام و چگونگی تشریح مسایل و شرایط به گونه ای که فرد متقاضی را راغب به ارسال رزومه یا نمونه کار نماید. این مرحله مهمتر از مرحله انتخاب است؛ به دلیل اینکه اگر نتوانیم متقاضیان کار را جذب کنیم، دامنه انتخاب بین افراد متقاضی کاهش پیدا میکند و گاهی مجبور خواهیم شد بین گزینههای بد و بدتر، بد را انتخاب نماییم.
مرحله انتخاب:
این مرحله برای گزینش افراد است که همگان از آن به عنوان مرحله مصاحبه یاد میکنند که مورد بحث ما نیست.
مواردی که در آگهی استخدام شرکت دلفین رعایت شده بود:
»ذکر نام شرکت
اولین مورد که در بسیاری از آگهیهای استخدام رعایت نمیشود عدم ذکر نام شرکت است. دیده میشود در آگهیها بیشتر از عناوینی نظیر "یک شرکت معتبر نرم افزاری" یا "یک شرکت فعال در زمینه تولید نرم افزار" و... استفاده میکنند. ذکر نام شرکت از آن جهت مهم است که فرد متقاضی میتواند با یک جستجوی ساده در اینترنت (در صورتی که شرکت تقاضا دهنده یک وب سایت مناسب در این زمینه داشته باشد) بسیاری از اطلاعات مهم نظیر زمینه فعالیت شرکت ، مشتریان عمده شرکت ، محصولات تولید شده قبلی یا حتی آدرس شرکت (برای بررسی وضعیت رفت و آمد) را به دست بیاورد. در آگهی استخدام ذکر شده این مورد به خوبی رعایت شده بود.
»ذکر آدرس و شماره تماس:
دلیل اصلی برای ذکر آدرس شرکت صرفا برای بررسی وضعیت رفت و آمد از نظر شرایط ترافیک شهری و بعد مسافت یا حتی شرایط رفت و آمد با خودروی شخصی است که برای بیشتر متقاضیان مهم است. در آگهی بالا نیز این مورد رعایت شده بود.
» ذکر شرایط استخدام
موردی که در آگهی بالا نظرم را جلب کرد تشریح کامل شرایطی بود که متقاضیان باید آن را داشته باشند. برای مثال ذکر شرایط سنی، شرایط مربوط به عدم اشتغال به تحصیل در دانشگاهها یا موسسات یا حتی شرایط مربوط به اخراج از دانشگاه ها. در این صورت برخی از متقاضیان ولو اندک در همین ابتدا فیلتر میشوند.
»ذکر شرایط تخصصی متقاضیان کار:
در بخش به صورت کامل شرایط تخصصی و مهارتی افراد مورد بررسی قرار گرفته بود. در یک نگاه خوب به نظر میرسید ولی اگر با دقت به این بخش نگاه کنید متوجه خواهید شد به دلیل عدم نگارش صحیح موارد مورد نیاز، کمتر کسی دارای این همه توانایی است آن هم به صورت تسلط کامل. استفاده از واژه تسلط کامل باید با احتیاط انجام شود. برای مثال تسلط کامل بر VB.NET , C#.NET Windows Application . بهتر بود از یا استفاده میشد. به این صورت " تسلط بر VB.NET یا C#.NET در Windows Application"
» ذکر اولویت ها
این مورد هم به خوبی در آگهی بالا رعایت شده بود. ذکر اولویتها باعث میشود که فرد متقاضی اگر دارای حتی یکی از این موارد است آن را در رزومه خود ذکر کند و از طرفی میتوان با توجه به اولویتهای استخدام تا حدودی شرایط کاری در شرکت متقاضی را در همین ابتدا بررسی کرد.
»ذکر آدرس وب سایت شرکت:
این مورد هم به خوبی در این آگهی رعایت شد و علاوه بر آن یک لینک به آدرس وب سایت سرپرست تیم برنامه نویسان نیز وجود داشت که امکان ارتباط مستقیم را فراهم میساخت.
مواردی که در آگهی استخدام شرکت دلفین رعایت نشده بود:
»عدم توازن بین عنوان آگهی با محتوای آن :
در عنوان آگهی ذکر شده بود "برنامه نویس تحت وب" در حالی که در شرایط تخصصی، موردی به عنوان تسلط کامل بر برنامه نویس تحت ویندوز ذکر شده آن هم با دو زبان Vb.Net و C#.Net
آیا منظور از تسلط کامل بر مدیریت بانک اطلاعاتی SqlServer همان DBA است؟ اگر پاسخ مثبت است در این صورت چند نفر واجد شرایط را میتوان پیدا کرد که هم در سطح DBA باشند و هم در سطح یک برنامه نویس سطح بالا؟ اگر پاسخ منفی است بهتر بود از واژه داشتن تجربه در زمینه مدیریت بانک اطلاعاتی یا حتی آشنایی با مدیریت بانکهای اطلاعاتی استفاده میشد.
مورد دیگری که تا حدودی باعث سردرگمی میشد این است که نیاز به تسلط کامل به Asp.Net Web Form و هم چنین به Asp.Net MVC است. چرا هردو، آن هم در سطح تسلط کامل؟
در بخش اولویتها موردی ذکر شد با عنوان آشنایی با کامپوننتهای برنامه نویسی نظیر DevExpress و Telerik و Kendo. آیا منظور آشنایی با تمام این موارد بوده است یا فقط یکی از آن ها؟
یک ضعف بزرگ که در اغلب آگهیها (از جمله در این آگهی نیز) وجود دارد، عدم ذکر شرایط شرکت برای متقاضیان است. بهتر بود بعد از ذکر این همه شرایط برای متقاضیان و برنامه نوسان، کمی هم درباره وضعیت حقوق و مزایا (برای مثال ذکر حداقل حقوق) و بیمه یا حتی شرایط رفاهی و خاص در نظر گرفته شده (که البته با توجه به وضعیت فعلی که دریافت حقوق ماهانه بدون تاخیر یک موهبت است این مورد بیشتر شبیه به رویا برای برنامه نویسان خواهد بود) صحبت میشد.
در صورتی که ذکر تمام این موارد در آگهی امکان پذیر نیست میتوان با ذکر یک لینک به وب سایت شرکت مورد نظر یا حتی ذکر شماره تلفن، متقاضی کار را قبل از ارسال رزومه از شرایط شرکت برای استخدام مطلع ساخت.
بررسی Hybrid Flow جهت امن سازی Web API
این Flow را پیشتر نیز مرور کرده بودیم. تفاوت آن با قسمتهای قبل، در استفاده از توکن دومی است به نام access token که به همراه identity token از طرف IDP صادر میشود و تا این قسمت از آن بجز در قسمت «دریافت اطلاعات بیشتری از کاربران از طریق UserInfo Endpoint» استفاده نکرده بودیم.
در اینجا، ابتدا برنامهی وب، یک درخواست اعتبارسنجی را به سمت IDP ارسال میکند که response type آن از نوع code id_token است (یا همان مشخصهی Hybrid Flow) و همچنین تعدادی scope نیز جهت دریافت claims متناظر با آنها در این درخواست ذکر شدهاند. در سمت IDP، کاربر با ارائهی مشخصات خود، اعتبارسنجی شده و پس از آن IDP صفحهی اجازهی دسترسی به اطلاعات کاربر (صفحهی consent) را ارائه میدهد. پس از آن IDP اطلاعات code و id_token را به سمت برنامهی وب ارسال میکند. در ادامه کلاینت وب، توکن هویت رسیده را اعتبارسنجی میکند. پس از موفقیت آمیز بودن این عملیات، اکنون کلاینت درخواست دریافت یک access token را از IDP ارائه میدهد. اینکار در پشت صحنه و بدون دخالت کاربر صورت میگیرد که به آن استفادهی از back channel هم گفته میشود. یک چنین درخواستی به token endpoint، شامل اطلاعات code و مشخصات دقیق کلاینت جاری است. به عبارتی نوعی اعتبارسنجی هویت برنامهی کلاینت نیز میباشد. در پاسخ، دو توکن جدید را دریافت میکنیم: identity token و access token. در اینجا access token توسط خاصیت at_hash موجود در id_token به آن لینک میشود. سپس هر دو توکن اعتبارسنجی میشوند. در این مرحله، میانافزار اعتبارسنجی، هویت کاربر را از identity token استخراج میکند. به این ترتیب امکان وارد شدن به برنامهی کلاینت میسر میشود. در اینجا همچنین access token ای نیز صادر شدهاست.
اکنون علاقمند به کار با Web API برنامهی کلاینت MVC خود هستیم. برای این منظور access token که اکنون در برنامهی MVC Client در دسترس است، به صورت یک Bearer token به هدر ویژهای با کلید Authorization اضافه میشود و به همراه هر درخواست، به سمت API ارسال خواهد شد. در سمت Web API این access token رسیده، اعتبارسنجی میشود و در صورت موفقیت آمیز بودن عملیات، دسترسی به منابع Web API صادر خواهد شد.
امن سازی دسترسی به Web API
تنظیمات برنامهی IDP
برای امن سازی دسترسی به Web API از کلاس src\IDP\DNT.IDP\Config.cs در سطح IDP شروع میکنیم. در اینجا باید یک scope جدید مخصوص دسترسی به منابع Web API را تعریف کنیم:
namespace DNT.IDP { public static class Config { // api-related resources (scopes) public static IEnumerable<ApiResource> GetApiResources() { return new List<ApiResource> { new ApiResource( name: "imagegalleryapi", displayName: "Image Gallery API", claimTypes: new List<string> {"role" }) }; }
پس از آن در قسمت تعریف کلاینت، مجوز درخواست این scope جدید imagegalleryapi را نیز صادر میکنیم:
AllowedScopes = { IdentityServerConstants.StandardScopes.OpenId, IdentityServerConstants.StandardScopes.Profile, IdentityServerConstants.StandardScopes.Address, "roles", "imagegalleryapi" },
namespace DNT.IDP { public class Startup { public void ConfigureServices(IServiceCollection services) { services.AddMvc(); services.AddIdentityServer() .AddDeveloperSigningCredential() .AddTestUsers(Config.GetUsers()) .AddInMemoryIdentityResources(Config.GetIdentityResources()) .AddInMemoryApiResources(Config.GetApiResources()) .AddInMemoryClients(Config.GetClients()); }
تنظیمات برنامهی MVC Client
اکنون نوبت انجام تنظیمات برنامهی MVC Client در فایل ImageGallery.MvcClient.WebApp\Startup.cs است. در اینجا در متد AddOpenIdConnect، درخواست scope جدید imagegalleryapi را صادر میکنیم:
options.Scope.Add("imagegalleryapi");
تنظیمات برنامهی Web API
اکنون میخواهیم مطمئن شویم که Web API، به access token ای که قسمت Audience آن درست مقدار دهی شدهاست، دسترسی خواهد داشت.
برای این منظور به پوشهی پروژهی Web API در مسیر src\WebApi\ImageGallery.WebApi.WebApp وارد شده و دستور زیر را صادر کنید تا بستهی نیوگت AccessTokenValidation نصب شود:
dotnet add package IdentityServer4.AccessTokenValidation
using IdentityServer4.AccessTokenValidation; namespace ImageGallery.WebApi.WebApp { public class Startup { public void ConfigureServices(IServiceCollection services) { services.AddAuthentication(defaultScheme: IdentityServerAuthenticationDefaults.AuthenticationScheme) .AddIdentityServerAuthentication(options => { options.Authority = Configuration["IDPBaseAddress"]; options.ApiName = "imagegalleryapi"; });
سپس متد AddIdentityServerAuthentication فراخوانی شدهاست که به آدرس IDP اشاره میکند که مقدار آنرا در فایل appsettings.json قرار دادهایم. از این آدرس برای بارگذاری متادیتای IDP استفاده میشود. کار دیگر این میانافزار، اعتبارسنجی access token رسیدهی به آن است. مقدار خاصیت ApiName آن، به نام API resource تعریف شدهی در سمت IDP اشاره میکند. هدف این است که بررسی شود آیا خاصیت aud موجود در access token رسیده به مقدار imagegalleryapi تنظیم شدهاست یا خیر؟
پس از تنظیم این میانافزار، اکنون نوبت به افزودن آن به ASP.NET Core request pipeline است:
namespace ImageGallery.WebApi.WebApp { public class Startup { public void Configure(IApplicationBuilder app, IHostingEnvironment env) { app.UseAuthentication();
اکنون میتوانیم اجبار به Authorization را در تمام اکشن متدهای این Web API در فایل ImageGallery.WebApi.WebApp\Controllers\ImagesController.cs فعالسازی کنیم:
namespace ImageGallery.WebApi.WebApp.Controllers { [Route("api/images")] [Authorize] public class ImagesController : Controller {
ارسال Access Token به همراه هر درخواست به سمت Web API
تا اینجا اگر مراحل اجرای برنامهها را طی کنید، مشاهده خواهید کرد که برنامهی MVC Client دیگر کار نمیکند و نمیتواند از فیلتر Authorize فوق رد شود. علت اینجا است که در حال حاضر، تمامی درخواستهای رسیدهی به Web API، فاقد Access token هستند. بنابراین اعتبارسنجی آنها با شکست مواجه میشود.
برای رفع این مشکل، سرویس ImageGalleryHttpClient را به نحو زیر اصلاح میکنیم تا در صورت وجود Access token، آنرا به صورت خودکار به هدرهای ارسالی توسط HttpClient اضافه کند:
using System; using System.Net.Http; using System.Net.Http.Headers; using System.Threading.Tasks; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Configuration; using Microsoft.IdentityModel.Protocols.OpenIdConnect; namespace ImageGallery.MvcClient.Services { public interface IImageGalleryHttpClient { Task<HttpClient> GetHttpClientAsync(); } /// <summary> /// A typed HttpClient. /// </summary> public class ImageGalleryHttpClient : IImageGalleryHttpClient { private readonly HttpClient _httpClient; private readonly IConfiguration _configuration; private readonly IHttpContextAccessor _httpContextAccessor; public ImageGalleryHttpClient( HttpClient httpClient, IConfiguration configuration, IHttpContextAccessor httpContextAccessor) { _httpClient = httpClient; _configuration = configuration; _httpContextAccessor = httpContextAccessor; } public async Task<HttpClient> GetHttpClientAsync() { var currentContext = _httpContextAccessor.HttpContext; var accessToken = await currentContext.GetTokenAsync(OpenIdConnectParameterNames.AccessToken); if (!string.IsNullOrWhiteSpace(accessToken)) { _httpClient.SetBearerToken(accessToken); } _httpClient.BaseAddress = new Uri(_configuration["WebApiBaseAddress"]); _httpClient.DefaultRequestHeaders.Accept.Clear(); _httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); return _httpClient; } } }
<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <TargetFramework>netstandard2.0</TargetFramework> </PropertyGroup> <ItemGroup> <PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="2.1.1.0" /> <PackageReference Include="Microsoft.AspNetCore.Http" Version="2.1.1.0" /> <PackageReference Include="Microsoft.AspNetCore.Authentication.Abstractions" Version="2.1.1.0" /> <PackageReference Include="Microsoft.IdentityModel.Protocols.OpenIdConnect" Version="5.2.0.0" /> <PackageReference Include="IdentityModel" Version="3.9.0" /> </ItemGroup> </Project>
البته پس از این تغییرات نیاز است به کنترلر گالری مراجعه و از متد جدید GetHttpClientAsync بجای خاصیت HttpClient قبلی استفاده کرد.
اکنون اگر برنامه را اجرا کنیم، پس از لاگین، دسترسی به Web API امن شده، برقرار شده و برنامه بدون مشکل کار میکند.
بررسی محتوای Access Token
اگر بر روی سطر if (!string.IsNullOrWhiteSpace(accessToken)) در سرویس ImageGalleryHttpClient یک break-point را قرار دهیم و محتویات Access Token را در حافظه ذخیره کنیم، میتوانیم با مراجعهی به سایت jwt.io، محتویات آنرا بررسی نمائیم:
که در حقیقت این محتوا را به همراه دارد:
{ "nbf": 1536394771, "exp": 1536398371, "iss": "https://localhost:6001", "aud": [ "https://localhost:6001/resources", "imagegalleryapi" ], "client_id": "imagegalleryclient", "sub": "d860efca-22d9-47fd-8249-791ba61b07c7", "auth_time": 1536394763, "idp": "local", "role": "PayingUser", "scope": [ "openid", "profile", "address", "roles", "imagegalleryapi" ], "amr": [ "pwd" ] }
همچنین اگر دقت کنید، Id کاربر جاری در خاصیت sub آن قرار دارد.
مدیریت صفحهی عدم دسترسی به Web API
با اضافه شدن scope جدید دسترسی به API در سمت IDP، این مورد در صفحهی دریافت رضایت کاربر نیز ظاهر میشود:
در این حالت اگر کاربر این گزینه را انتخاب نکند، پس از هدایت به برنامهی کلاینت، در سطر response.EnsureSuccessStatusCode استثنای زیر ظاهر خواهد شد:
An unhandled exception occurred while processing the request. HttpRequestException: Response status code does not indicate success: 401 (Unauthorized). System.Net.Http.HttpResponseMessage.EnsureSuccessStatusCode()
public async Task<IActionResult> Index() { var httpClient = await _imageGalleryHttpClient.GetHttpClientAsync(); var response = await httpClient.GetAsync("api/images"); if (response.StatusCode == System.Net.HttpStatusCode.Unauthorized || response.StatusCode == System.Net.HttpStatusCode.Forbidden) { return RedirectToAction("AccessDenied", "Authorization"); } response.EnsureSuccessStatusCode();
فیلتر کردن تصاویر نمایش داده شده بر اساس هویت کاربر وارد شدهی به سیستم
تا اینجا هرچند دسترسی به API امن شدهاست، اما هنوز کاربر وارد شدهی به سیستم میتواند تصاویر سایر کاربران را نیز مشاهده کند. بنابراین قدم بعدی امن سازی API، عکس العمل نشان دادن به هویت کاربر جاری سیستم است.
برای این منظور به کنترلر ImageGallery.WebApi.WebApp\Controllers\ImagesController.cs سمت API مراجعه کرده و Id کاربر جاری را از لیست Claims او استخراج میکنیم:
namespace ImageGallery.WebApi.WebApp.Controllers { [Route("api/images")] [Authorize] public class ImagesController : Controller { [HttpGet()] public async Task<IActionResult> GetImages() { var ownerId = this.User.Claims.FirstOrDefault(claim => claim.Type == "sub").Value;
مرحلهی بعد، مراجعه به ImageGallery.WebApi.Services\ImagesService.cs و تغییر متد GetImagesAsync است تا صرفا بر اساس ownerId دریافت شده کار کند:
namespace ImageGallery.WebApi.Services { public class ImagesService : IImagesService { public Task<List<Image>> GetImagesAsync(string ownerId) { return _images.Where(image => image.OwnerId == ownerId).OrderBy(image => image.Title).ToListAsync(); }
namespace ImageGallery.WebApi.WebApp.Controllers { [Route("api/images")] [Authorize] public class ImagesController : Controller { [HttpGet()] public async Task<IActionResult> GetImages() { var ownerId = this.User.Claims.FirstOrDefault(claim => claim.Type == "sub").Value; var imagesFromRepo = await _imagesService.GetImagesAsync(ownerId); var imagesToReturn = _mapper.Map<IEnumerable<ImageModel>>(imagesFromRepo); return Ok(imagesToReturn); }
هنوز یک مشکل دیگر باقی است: سایر اکشن متدهای این کنترلر Web API همچنان محدود به کاربر جاری نشدهاند. یک روش آن تغییر دستی تمام کدهای آن است. در این حالت متد IsImageOwnerAsync زیر، جهت بررسی اینکه آیا رکورد درخواستی متعلق به کاربر جاری است یا خیر، به سرویس تصاویر اضافه میشود:
namespace ImageGallery.WebApi.Services { public class ImagesService : IImagesService { public Task<bool> IsImageOwnerAsync(Guid id, string ownerId) { return _images.AnyAsync(i => i.Id == id && i.OwnerId == ownerId); }
اما روش بهتر انجام این عملیات را که در قسمت بعدی بررسی میکنیم، بر اساس بستن دسترسی ورود به اکشن متدها بر اساس Authorization policy است. در این حالت اگر کاربری مجوز انجام عملیاتی را نداشت، اصلا وارد کدهای یک اکشن متد نخواهد شد.
ارسال سایر User Claims مانند نقشها به همراه یک Access Token
برای تکمیل قسمت ارسال تصاویر میخواهیم تنها کاربران نقش خاصی قادر به انجام اینکار باشند. اما اگر به محتوای access token ارسالی به سمت Web API دقت کرده باشید، حاوی Identity claims نیست. البته میتوان مستقیما در برنامهی Web API با UserInfo Endpoint، برای دریافت اطلاعات بیشتر، کار کرد که نمونهای از آنرا در قسمت قبل مشاهده کردید، اما مشکل آن زیاد شدن تعداد رفت و برگشتهای به سمت IDP است. همچنین باید درنظر داشت که فراخوانی مستقیم UserInfo Endpoint جهت برنامهی MVC client که درخواست دریافت access token را از IDP میدهد، متداول است و نه برنامهی Web API.
برای رفع این مشکل باید در حین تعریف ApiResource، لیست claim مورد نیاز را هم ذکر کرد:
namespace DNT.IDP { public static class Config { // api-related resources (scopes) public static IEnumerable<ApiResource> GetApiResources() { return new List<ApiResource> { new ApiResource( name: "imagegalleryapi", displayName: "Image Gallery API", claimTypes: new List<string> {"role" }) }; }
سپس کار با اکشن متد CreateImage در سمت API را به نقش PayingUser محدود میکنیم:
namespace ImageGallery.WebApi.WebApp.Controllers { [Route("api/images")] [Authorize] public class ImagesController : Controller { [HttpPost] [Authorize(Roles = "PayingUser")] public async Task<IActionResult> CreateImage([FromBody] ImageForCreationModel imageForCreation) {
var ownerId = User.Claims.FirstOrDefault(c => c.Type == "sub").Value; imageEntity.OwnerId = ownerId; // add and save. await _imagesService.AddImageAsync(imageEntity);
نکتهی مهم: در اینجا نباید این OwnerId را از سمت برنامهی کلاینت MVC به سمت برنامهی Web API ارسال کرد. برنامهی Web API باید این اطلاعات را از access token اعتبارسنجی شدهی رسیده استخراج و استفاده کند؛ از این جهت که دستکاری اطلاعات اعتبارسنجی نشدهی ارسالی به سمت Web API سادهاست؛ اما access tokenها دارای امضای دیجیتال هستند.
در سمت کلاینت نیز در فایل ImageGallery.MvcClient.WebApp\Views\Shared\_Layout.cshtml نمایش لینک افزودن تصویر را نیز محدود به PayingUser میکنیم:
@if(User.IsInRole("PayingUser")) { <li><a asp-area="" asp-controller="Gallery" asp-action="AddImage">Add an image</a></li> <li><a asp-area="" asp-controller="Gallery" asp-action="OrderFrame">Order a framed picture</a></li> }
namespace ImageGallery.MvcClient.WebApp.Controllers { [Authorize] public class GalleryController : Controller { [Authorize(Roles = "PayingUser")] public IActionResult AddImage() { return View(); }
[HttpPost] [Authorize(Roles = "PayingUser")] [ValidateAntiForgeryToken] public async Task<IActionResult> AddImage(AddImageViewModel addImageViewModel)
برای آزمایش این قسمت یکبار از برنامه خارج شده و سپس با اکانت User 1 که PayingUser است به سیستم وارد شوید. در ادامه از منوی بالای سایت، گزینهی Add an image را انتخاب کرده و تصویری را آپلود کنید. پس از آن، این تصویر آپلود شده را در لیست تصاویر صفحهی اول سایت، مشاهده خواهید کرد.
کدهای کامل این قسمت را از اینجا میتوانید دریافت کنید.
برای اجرای برنامه:
- ابتدا به پوشهی src\WebApi\ImageGallery.WebApi.WebApp وارد شده و dotnet_run.bat آنرا اجرا کنید تا WebAPI برنامه راه اندازی شود.
- سپس به پوشهی src\IDP\DNT.IDP مراجعه کرده و و dotnet_run.bat آنرا اجرا کنید تا برنامهی IDP راه اندازی شود.
- در آخر به پوشهی src\MvcClient\ImageGallery.MvcClient.WebApp وارد شده و dotnet_run.bat آنرا اجرا کنید تا MVC Client راه اندازی شود.
اکنون که هر سه برنامه در حال اجرا هستند، مرورگر را گشوده و مسیر https://localhost:5001 را درخواست کنید. در صفحهی login نام کاربری را User 1 و کلمهی عبور آنرا password وارد کنید.
دوره و کتاب رایگان Angular 5
EF Code First #12
در متد Program.Main هم میتونید تنظیمات اولیه ObjectFactory رو قرار بدید (شبیه به Application_Start برنامههای وب).
بنابراین در برنامههای وب «context/session per http request» داریم؛ در برنامههای ویندوزی «context per operation or per form». یعنی میتونید بسته به معماری برنامه ویندوزی خود، context را در سطح یک فرم تعریف کنید و مدیریت؛ و یا در سطح یک عملیات کوتاه مانند یک کلیک. تمام مباحث ObjectFactory.GetInstance، uow.SaveChanges و یا Dispose آن هم دستی است و زیر ساختی برای مدیریت خودکار آنها همانند برنامههای مثلا ASP.NET MVC وجود ندارد. حداکثر اینکه یک سری base class را شبیه به مثال Web forms زده شده تهیه کنید، تا میزان کدهای تکراری را کاهش بدید.