- البته در حالت دستی عنوان شده امکان تعریف آیکون و غیره هم هست. در متن، لینک داده شده به مراجع تولید فایلهای vstemplate و vscontent که برای نمونه یک مدخل اضافهتر برای آیکون پیدا میکند:
<Icon>__Template_small.png</Icon>
<Icon>__Template_small.png</Icon>
postTicket(ticket: Ticket, filesList: FileList): Observable<HttpEvent<any>> { //… the same as before const headers = new HttpHeaders().set("Accept", "application/json"); return this.http .post(`${this.baseUrl}/SaveTicket`, formData, { headers: headers, reportProgress: true, observe: "events" }) .map(response => response || {}) .catch((error: HttpErrorResponse) => { console.error("observable error: ", error); return Observable.throw(error.statusText); }); }
postTicket(ticket: Ticket, filesList: FileList): Observable<HttpEvent<any>> {
{ headers: headers, reportProgress: true, observe: "events" }
queueProgress: number; isUploading: boolean; uploadTimeRemaining: number; uploadTimeElapsed: number; uploadSpeed: number; submitForm(form: NgForm) { const fileInput: HTMLInputElement = this.screenshotInput.nativeElement; this.queueProgress = 0; this.isUploading = true; let startTime = Date.now(); this.uploadService.postTicket(this.model, fileInput.files).subscribe( (event: HttpEvent<any>) => { switch (event.type) { case HttpEventType.Sent: startTime = Date.now(); console.log("Request sent!"); break; case HttpEventType.DownloadProgress: case HttpEventType.UploadProgress: if (event.total) { this.queueProgress = Math.round(event.loaded / event.total * 100); const timeElapsed = Date.now() - startTime; const uploadSpeed = event.loaded / (timeElapsed / 1000); this.uploadTimeRemaining = Math.ceil( (event.total - event.loaded) / uploadSpeed ); this.uploadTimeElapsed = Math.ceil(timeElapsed / 1000); this.uploadSpeed = uploadSpeed / 1024 / 1024; } break; case HttpEventType.Response: this.queueProgress = 100; this.isUploading = false; console.log("Done! ResponseBody:", event.body); break; } }, (error: HttpErrorResponse) => { this.isUploading = false; console.log(error); } ); }
<div *ngIf="queueProgress > 0"> <table class="table"> <thead> <tr> <th width="15%">Event</th> <th>Status</th> </tr> </thead> <tbody> <tr> <td><strong>Elapsed time</strong></td> <td nowrap>{{uploadTimeElapsed | number:'.1'}} second(s)</td> </tr> <tr> <td><strong>Remaining time</strong></td> <td nowrap>{{uploadTimeRemaining | number:'.1'}} second(s)</td> </tr> <tr> <td><strong>Upload speed</strong></td> <td nowrap>{{uploadSpeed | number:'.3'}} MB/s</td> </tr> <tr> <td><strong>Queue progress</strong></td> <td> <div class="progress-bar progress-bar-info progress-bar-striped" role="progressbar" aria-valuemin="0" aria-valuemax="100" [attr.aria-valuenow]="queueProgress" [ngStyle]="{ 'width': queueProgress + '%' }"> {{queueProgress}}% </div> </td> </tr> </tbody> </table> </div> <button class="btn btn-primary" [disabled]="form.invalid || isUploading" type="submit">Submit</button>
دانای اطلاعات ( Information Expert )
بر طبق این اصل میتوان برای واگذاری هر مسئولیت، کلاسی را انتخاب کرد که بیشترین اطلاعات را در مورد انجام آن در اختیار دارد و لذا نیاز کمتری به ایجاد ارتباط با دیگر مولفهها خواهد داشت.
در مثال زیر مشاهده میکنید که کلاس User، اطلاعات کاملی را از عملیات اضافه کردن آیتمی را به لیست خرید و تسویه حساب، ندارد و پیاده سازی این عملیات در این کلاس، نیاز به ایجاد وابستگیهای پیچیدهای دارد.
public class User { public ShoppingCart ShoppingCart { get; set; } public void AddItem(string name) { // User class must know how to create OrderItem var item = new OrderItem() { Name = name }; // User class must know how to add item to shopping cart ShoppingCart.Items.Add(item); } public void CheckOut() { // User class must know logic behind cost and discount calculations: // check for discount // check shipping method // check promotions // calculate total cost of items } } public class OrderItem { public int Id { get; set; } public string Name { get; set; } } public class ShoppingCart { public int Id { get; set; } public List<OrderItem> Items { get; set; } }
بنابراین به جای این طراحی، مسئولیتها را به ShoppingCart منتقل میکنیم:
public class User { public ShoppingCart ShoppingCart { get; set; } } public class OrderItem { public int Id { get; set; } public string Name { get; set; } } public class ShoppingCart { public int Id { get; set; } public List<OrderItem> Items { get; set; } public void AddItem(string name) { // ShoppingCart class know how to create OrderItem var item = new OrderItem() { Name = name }; // ShoppingCart class already know how to add item Items.Add(item); } public void CheckOut() { // ShoppingCart class know logic behind cost and discount calculations: // check for discount // check shipping method // check promotions // calculate total cost of items } }
اتصال ضعیف ( Low Coupling )
با اتصال ضعیف نیز که از ویژگیهای یک طراحی خوب است آشنا هستیم. هر چه تعداد و نوع اتصال بین مولفهها کمتر و ضعیفتر باشد، اعمال تغییرات راحتتر صورت خواهد گرفت. طراحی با اتصال مناسب سه ویژگی را دارد:
- وابستگی بین کلاسها کم است.
- تغییرات در یک کلاس، اثر کمی بر دیگر کلاسها دارد.
- پتانسیل استفادهی مجدد از مؤلفهها بالا است.
چنانچه قبلا هم اشاره کردم، نوشتن نرم افزاری بدون اتصال، ممکن نیست و باید مؤلفهها با هم همکاری کرده و وظایف را انجام دهند. با این حال میتوان نوع اتصالات و تعداد آنرا بهبود بخشید.
چند ریختی ( Polymorphism )
چند ریختی که از ویژگیهای اساسی برنامه نویسی و زبانهای شیء گراست، به منظور بالا بردن قابلیت استفادهی مجدد، استفاده میشود. بر طبق این اصل، مسئولیت تعریف رفتارهای وابسته به نوع کلاس (زیرنوعها در روابط ارث بری) باید به کلاسی واگذار شود که تغییر رفتار در آن اتفاق میافتد. به عبارت دیگر باید به صورت خودکار رفتار را بر اساس نوع کلاس تصحیح کنیم. این روش در مقابل بررسی نوع دادهای برای انجام رفتار مناسب میباشد.
به عنوان مثال اگر کلاسهای چهار ضلعی، مربع، مستطیل و ذوزنقه را داشته باشیم، برای پیاده سازی مساحت در کلاس چهار ضلعی، طول را در عرض ضرب میکنیم. با این حال نوع رفتار مساحت ذوزنقه متفاوت از دیگران است. طبق این اصل، برای اعمال کردن این تغییر، فقط خود کلاس ذوزنقه باید رفتار مربوطه را پیاده سازی کند و هیچ منطق و کدی نباید برای چک کردن نوع کلاس استفاده گردد.
public class ShapeWithoutPolymorphism { public double X { get; set; } public double Y { get; set; } public double Z { get; set; } public double Area(string shapeType) { switch (shapeType) { case "square": return X * Y; case "rectangle": return X * Y; case "trapze": return (X + Z) * Y / 2; default: return 0; } } }
با استفاده از چندریختی، طراحی به این صورت در خواهد آمد:
public abstract class Shape { public double X { get; set; } public double Y { get; set; } public virtual double Area() { return X * Y; } } public class Rectangle : Shape { // No need to override } public class Square : Shape { // No need to override } public class Trapze : Shape { public double Z { get; set; } public override double Area() { return (X + Z) * Y / 2; } }
مصنوع خالص ( Pure Fabrication )
مصنوع خالص کلاسی است که در دامنه مساله وجود ندارد و به منظور کاهش اتصال، افزایش انسجام و افزایش امکان استفاده مجدد کد ایجاد میشود. سرویسها را میتوان از این دسته نامید. کلاسهایی که دقیقا برای کاهش اتصال، افزایش انسجام و افزایش امکان استفاده مجدد کد استفاده میگردند. سرویسها عملیاتی تکراری هستند که توسط مولفههای دیگر استفاده میشوند. اگر سرویسها وجود نداشتند هر مولفهای میبایست عملیات را در درون خود پیاده سازی میکرد که این هم باعث افزایش حجم کد و هم باعث کابوس شدن اعمال تغییرات میشد.
برای تشخیص زمان استفاده از این اصل میتوان گفت زمانیکه رفتاری را نمیدانیم به کدام کلاس واگذار کنیم، کلاس جدیدی را ایجاد میکنیم. در اینجا بجای آنکه به زور مسئولیتی را به کلاس نامربوطی بچسبانیم، آنرا به کلاس جدیدی که فقط رفتاری را دارد، منتقل میکنیم. با اینکار انسجام کلاسها را حفظ کردهایم و هیچ اشکالی ندارد که کلاسی بدون داده بوده و فقط متد داشته باشد. اگر به یاد داشته باشید، در اصل واسطه گری (Indirection ) کلاس جدیدی برای ایجاد ارتباط ساختیم. در حقیقت مسئولیت برقراری ارتباط بین مؤلفهها را به کلاس دیگری واگذار کردیم که چنانچه میبینید، بدون آنکه بدانیم، برای حل مشکل از اصل مصنوع خالص استفاده کردیم.
در مثال زیر این مساله مشهود است:
public class User { public int Id { get; set; } public string UserName { get; set; } public string Password { get; set; } } public class LibraryManagement { public User CurrentUser { get; set; } public void AddBookToLibrary(int bookId) { // check for CurrentUser authority: // not user's responsibility nor LibraryManagement } public void RearrangeBook(int bookId, int shelfId) { // check for CurrentUser authority // not user's responsibility nor LibraryManagement } } public class UserManagement { public User CurrentUser { get; set; } public void AddUser(string name) { // check for CurrentUser authority: // not user's responsibility nor UserManagement } public void ChangeUserRole(int userId, int roleId) { // check for CurrentUser authority // not user's responsibility nor UserManagement } } public class AuthorizationService { public bool IsAuthorized(int userId, int roleId) { // get user roles from data base // return true if user has the authority } }
عملیات بررسی مجوزها باید در کلاس جدیدی به نام AuthorizationService ارائه شود. بدین صورت تمام قسمتها، از این کد بدون وابستگی اضافی میتوانند استفاده کنند.
حفاظت از تاثیر تغییرات ( Protected Variations )
این اصل میگوید که کلاسها باید از تغییرات یکدیگر مصون بمانند. در واقع این اصل غایت یک طراحی خوب است. تمام اصولی را که تا به حال بررسی کردهایم، به منظور دستیابی به چنین رفتاری از طراحی بودهاست. بدین منظور باید از اصول Open/Closed برای واسطها، چند ریختی در توارث و ... استفاده کرد تا از تاثیرات زنجیرهای تغییرات در امان بمانیم.
@if (ViewData.ModelState.Any(keyValuePair => keyValuePair.Value.Errors.Any())) { <div class="alert alert-danger"> <a href="#" class="close" data-dismiss="alert">×</a> <h4>خطاهای اعتبارسنجی</h4> <div asp-validation-summary="ModelOnly"></div> </div> }
public interface IEnumerator<out T> : IEnumerator, IDisposable { // // Summary: // Gets the element in the collection at the current position of the enumerator. // // Returns: // The element in the collection at the current position of the enumerator. T Current { get; } }
Error CS1579 foreach statement cannot operate on variables of type ‘IEnumerator’ because ‘IEnumerator’ does not contain a public instance or extension definition for ‘GetEnumerator’
static class Extensions { public static IEnumerator<T> GetEnumerator<T>(this IEnumerator<T> enumerator) => enumerator; }
class Program { void Main() { var enumerator = Enumerable.Range(0, 10).GetEnumerator(); foreach (var item in enumerator) { Console.WriteLine(item); } } }
static class Extensions { public static IAsyncEnumerator<T> GetAsyncEnumerator<T>(this IAsyncEnumerator<T> enumerator) => enumerator; } class Program { static async Task Main() { var enumerator = GetAsyncEnumerator(); await foreach (var item in enumerator) { Console.WriteLine(item); } } static async IAsyncEnumerator<int> GetAsyncEnumerator() { yield return 0; await Task.Delay(1); yield return 1; } }
class Program { static void Main() { foreach (var item in (1, 2, 3)) { Console.WriteLine(item); } } }
foreach statement cannot operate on variables of type '(int, int, int)' because '(int, int, int)' does not contain a public instance or extension definition for 'GetEnumerator' [CS9Features]csharp(CS1579)
static class Extensions { public static IEnumerator<object> GetEnumerator<T1, T2, T3>(this ValueTuple<T1, T2, T3> tuple) { yield return tuple.Item1; yield return tuple.Item2; } }
ویژوال استودیو را اجرا کنید و پروژه جدیدی از نوع ASP.NET Web Forms Application ایجاد کنید.
کلاس جدیدی با نام Product بسازید و خواص زیر را به آن اضافه کنید.
public class Product { public int Id { get; set; } public string Name { get; set; } public decimal Price { get; set; } public string Category { get; set; } }
در دیالوگ باز شده گزینه Web را از پانل سمت چپ کلیک کنید و نوع آیتم جدید را Web API Controller Class انتخاب نمایید. نام این کنترلر را به "ProductsController" تغییر دهید و OK کنید.
کنترلر ایجاد شده شامل یک سری متد است که بصورت خودکار برای شما اضافه شده اند، آنها را حذف کنید و کد زیر را به کنترلر خود اضافه کنید.
namespace WebForms { using System; using System.Collections.Generic; using System.Linq; using System.Net; using System.Net.Http; using System.Web.Http; 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 Product GetProductById(int id) { var product = products.FirstOrDefault((p) => p.Id == id); if (product == null) { throw new HttpResponseException(HttpStatusCode.NotFound); } return product; } public IEnumerable<Product> GetProductsByCategory(string category) { return products.Where( (p) => string.Equals(p.Category, category, StringComparison.OrdinalIgnoreCase)); } } }
مرحله بعدی اضافه کردن اطلاعات مسیریابی (routing) است. در مثال جاری میخواهیم آدرس هایی مانند "api/products/" به کنترلر Web API نگاشت شوند. فایل Global.asax را باز کنید و عبارت زیر را به بالای آن اضافه نمایید.
using System.Web.Http;
RouteTable.Routes.MapHttpRoute( name: "DefaultApi", routeTemplate: "api/{controller}/{id}", defaults: new { id = System.Web.Http.RouteParameter.Optional } );
تا اینجا شما یک API دارید که کلاینتها میتوانند به آن دسترسی داشته باشند. حال یک صفحهHTML خواهیم ساخت که با استفاده از jQuery سرویس را فراخوانی میکند. صفحه Default.aspx را باز کنید و کدی که بصورت خودکار در قسمت Content تولید شده است را حذف کرده و کد زیر را به این قسمت اضافه کنید:
<%@ Page Title="Home Page" Language="C#" MasterPageFile="~/Site.Master" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="WebForms._Default" %> <asp:Content ID="HeaderContent" runat="server" ContentPlaceHolderID="HeadContent"> </asp:Content> <asp:Content ID="BodyContent" runat="server" ContentPlaceHolderID="MainContent"> <h2>Products</h2> <table> <thead> <tr><th>Name</th><th>Price</th></tr> </thead> <tbody id="products"> </tbody> </table> </asp:Content>
<asp:Content ID="HeaderContent" runat="server" ContentPlaceHolderID="HeadContent"> <script src="Scripts/jquery-1.7.1.min.js" type="text/javascript"></script> </asp:Content>
زیر تگ jQuery اسکریپت زیر را اضافه کنید.
<script type="text/javascript"> function getProducts() { $.getJSON("api/products", function (data) { $('#products').empty(); // Clear the table body. // Loop through the list of products. $.each(data, function (key, val) { // Add a table row for the product. var row = '<td>' + val.Name + '</td><td>' + val.Price + '</td>'; $('<tr/>', { text: row }) // Append the name. .appendTo($('#products')); }); }); } $(document).ready(getProducts); </script>
Service Layer
نقش لایهی سرویس این است که به عنوان یک مدخل ورودی به برنامه کاربردی عمل کند. در برخی مواقع این لایه را به عنوان لایهی Facade نیز میشناسند. این لایه، دادهها را در قالب یک نوع داده ای قوی (Strongly Typed) به نام View Model، برای لایهی Presentation فراهم میکند. کلاس View Model یک Strongly Typed محسوب میشود که نماهای خاصی از دادهها را که متفاوت از دید یا نمای تجاری آن است، بصورت بهینه ارائه مینماید. در مورد الگوی View Model در مباحث بعدی بیشتر صحبت خواهم کرد.
الگوی Facade یک Interface ساده را به منظور کنترل دسترسی به مجموعه ای از Interfaceها و زیر سیستمهای پیچیده ارائه میکند. در مباحث بعدی در مورد آن بیشتر صحبت خواهم کرد.
کلاسی با نام ProductViewModel را با کد زیر به پروژه SoCPatterns.Layered.Service اضافه کنید:
public class ProductViewModel { Public int ProductId {get; set;} public string Name { get; set; } public string Rrp { get; set; } public string SellingPrice { get; set; } public string Discount { get; set; } public string Savings { get; set; } }
برای اینکه کلاینت با لایهی سرویس در تعامل باشد باید از الگوی Request/Response Message استفاده کنیم. بخش Request توسط کلاینت تغذیه میشود و پارامترهای مورد نیاز را فراهم میکند. کلاسی با نام ProductListRequest را با کد زیر به پروژه SoCPatterns.Layered.Service اضافه کنید:
using SoCPatterns.Layered.Model; namespace SoCPatterns.Layered.Service { public class ProductListRequest { public CustomerType CustomerType { get; set; } } }
در شی Response نیز بررسی میکنیم که درخواست به درستی انجام شده باشد، دادههای مورد نیاز را برای کلاینت فراهم میکنیم و همچنین در صورت عدم اجرای صحیح درخواست، پیام مناسب را به کلاینت ارسال مینماییم. کلاسی با نام ProductListResponse را با کد زیر به پروژه SoCPatterns.Layered.Service اضافه کنید:
public class ProductListResponse { public bool Success { get; set; } public string Message { get; set; } public IList<ProductViewModel> Products { get; set; } }
به منظور تبدیل موجودیت Product به ProductViewModel، به دو متد نیاز داریم، یکی برای تبدیل یک Product و دیگری برای تبدیل لیستی از Product. شما میتوانید این دو متد را به کلاس Product موجود در Domain Model اضافه نمایید، اما این متدها نیاز واقعی منطق تجاری نمیباشند. بنابراین بهترین انتخاب، استفاده از Extension Methodها میباشد که باید برای کلاس Product و در لایهی سرویس ایجاد نمایید. کلاسی با نام ProductMapperExtensionMethods را با کد زیر به پروژه SoCPatterns.Layered.Service اضافه کنید:
public static class ProductMapperExtensionMethods { public static ProductViewModel ConvertToProductViewModel(this Model.Product product) { ProductViewModel productViewModel = new ProductViewModel(); productViewModel.ProductId = product.Id; productViewModel.Name = product.Name; productViewModel.RRP = String.Format(“{0:C}”, product.Price.RRP); productViewModel.SellingPrice = String.Format(“{0:C}”, product.Price.SellingPrice); if (product.Price.Discount > 0) productViewModel.Discount = String.Format(“{0:C}”, product.Price.Discount); if (product.Price.Savings < 1 && product.Price.Savings > 0) productViewModel.Savings = product.Price.Savings.ToString(“#%”); return productViewModel; } public static IList<ProductViewModel> ConvertToProductListViewModel( this IList<Model.Product> products) { IList<ProductViewModel> productViewModels = new List<ProductViewModel>(); foreach(Model.Product p in products) { productViewModels.Add(p.ConvertToProductViewModel()); } return productViewModels; } }
حال کلاس ProductService را جهت تعامل با کلاس سرویس موجود در Domain Model و به منظور برگرداندن لیستی از محصولات و تبدیل آن به لیستی از ProductViewModel، ایجاد مینماییم. کلاسی با نام ProductService را با کد زیر به پروژه SoCPatterns.Layered.Service اضافه کنید:
public class ProductService { private Model.ProductService _productService; public ProductService(Model.ProductService ProductService) { _productService = ProductService; } public ProductListResponse GetAllProductsFor( ProductListRequest productListRequest) { ProductListResponse productListResponse = new ProductListResponse(); try { IList<Model.Product> productEntities = _productService.GetAllProductsFor(productListRequest.CustomerType); productListResponse.Products = productEntities.ConvertToProductListViewModel(); productListResponse.Success = true; } catch (Exception ex) { // Log the exception… productListResponse.Success = false; // Return a friendly error message productListResponse.Message = ex.Message; } return productListResponse; } }
کلاس Service تمامی خطاها را دریافت نموده و پس از مدیریت خطا، پیغامی مناسب را به کلاینت ارسال میکند. همچنین این لایه محل مناسبی برای Log کردن خطاها میباشد. در اینجا کد نویسی لایه سرویس به پایان رسید و در ادامه به کدنویسی Data Layer میپردازیم.
Data Layer
برای ذخیره سازی محصولات، یک بانک اطلاعاتی با نام Shop01 ایجاد کنید که شامل جدولی به نام Product با ساختار زیر باشد:
برای اینکه کدهای بانک اطلاعاتی را سریعتر تولید کنیم از روش Linq to SQL در Data Layer استفاده میکنم. برای این منظور یک Data Context برای Linq to SQL به این لایه اضافه میکنیم. بر روی پروژه SoCPatterns.Layered.Repository کلیک راست نمایید و گزینه Add > New Item را انتخاب کنید. در پنجره ظاهر شده و از سمت چپ گزینه Data و سپس از سمت راست گزینه Linq to SQL Classes را انتخاب نموده و نام آن را Shop.dbml تعیین نمایید.
از طریق پنجره Server Explorer به پایگاه داده مورد نظر متصل شوید و با عمل Drag & Drop جدول Product را به بخش Design کشیده و رها نمایید.
اگر به یاد داشته باشید، در لایه Model برای برقراری ارتباط با پایگاه داده از یک Interface به نام IProductRepository استفاده نمودیم. حال باید این Interface را پیاده سازی نماییم. کلاسی با نام ProductRepository را با کد زیر به پروژه SoCPatterns.Layered.Repository اضافه کنید:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using SoCPatterns.Layered.Model; namespace SoCPatterns.Layered.Repository { public class ProductRepository : IProductRepository { public IList<Model.Product> FindAll() { var products = from p in new ShopDataContext().Products select new Model.Product { Id = p.ProductId, Name = p.ProductName, Price = new Model.Price(p.Rrp, p.SellingPrice) }; return products.ToList(); } } }
در متد FindAll، با استفاده از دستورات Linq to SQL، لیست تمامی محصولات را برگرداندیم. کدنویسی لایهی Data هم به پایان رسید و در ادامه به کدنویسی لایهی Presentation و UI میپردازیم.
Presentation Layer
به منظور جداسازی منطق نمایش (Presentation) از رابط کاربری (User Interface)، از الگوی Model View Presenter یا همان MVP استفاده میکنیم که در مباحث بعدی با جزئیات بیشتری در مورد آن صحبت خواهم کرد. یک Interface با نام IProductListView را با کد زیر به پروژه SoCPatterns.Layered.Presentation اضافه کنید:
using SoCPatterns.Layered.Service; public interface IProductListView { void Display(IList<ProductViewModel> Products); Model.CustomerType CustomerType { get; } string ErrorMessage { set; } }
این Interface توسط Web Formهای ASP.NET و یا Win Formها باید پیاده سازی شوند. کار با Interfaceها موجب میشود تا تست Viewها به راحتی انجام شوند. کلاسی با نام ProductListPresenter را با کد زیر به پروژه SoCPatterns.Layered.Presentation اضافه کنید:
using SoCPatterns.Layered.Service; namespace SoCPatterns.Layered.Presentation { public class ProductListPresenter { private IProductListView _productListView; private Service.ProductService _productService; public ProductListPresenter(IProductListView ProductListView, Service.ProductService ProductService) { _productService = ProductService; _productListView = ProductListView; } public void Display() { ProductListRequest productListRequest = new ProductListRequest(); productListRequest.CustomerType = _productListView.CustomerType; ProductListResponse productResponse = _productService.GetAllProductsFor(productListRequest); if (productResponse.Success) { _productListView.Display(productResponse.Products); } else { _productListView.ErrorMessage = productResponse.Message; } } } }
کلاس Presenter وظیفهی واکشی داده ها، مدیریت رویدادها و بروزرسانی UI را دارد. در اینجا کدنویسی لایهی Presentation به پایان رسیده است. از مزایای وجود لایهی Presentation این است که تست نویسی مربوط به نمایش دادهها و تعامل بین کاربر و سیستم به سهولت انجام میشود بدون آنکه نگران دشواری Unit Test نویسی Web Formها باشید. حال میتوانید کد نویسی مربوط به UI را انجام دهید که در ادامه به کد نویسی در Win Forms و Web Forms خواهیم پرداخت.
grid.Column(columnName: "Description", header: "شرح", format: item => @Html.Raw( $"<a data-toggle='modal' class='fa fa-id-card-o' href={@renderModalPartialViewUrl+'/'+item.Id} data-target='#myModal'></a>")),