- ایا LocalDB جایگزینی برای SqlExpress خواهد بود در ادامه؟ (اینجا انتهای پاراگراف اول منظورش چیه دقیقا؟
SQL Server ExpressLocalDB should be used in place of the SQL Server Express user instance feature which is deprecated )
- هیچ شکلی نمیتوان تحت شبکه کار کرد با این نسخه؟ مثلا جایی که قراره 3 تا سیستم بصورت کلاینت/سرور کار کنن نمیشه فایل دیتابیس روی یک سیستم باشه و از رو 2تای دیگه بهش کانکت شد؟ سنارویی که خیلی وقتا ممکنه استفاده بشه
- بهترین راه برای سناریوهایی مثل مورد 2 که مثال زدم چیه؟ (یک محیط و یک برنامه معمولی که قرار نیست sql server بصورت full نصب بشه رو سرور) آیا میشه با خیال راحت از SqlServer Express Edition استفاده کرد؟
محدودیتهای کار با اشیاء COM در NET Core 2x.
پیاده سازی پشتیبانی از اشیاء COM در NET Core 2x. به همراه اینترفیس IDispatch نیست. به این معنا که از مفهوم «late binding» پشتیبانی نمیکند. حدود 10 سال قبل در زمان ارائهی C# 4.0، واژهی کلیدی dynamic نیز ارائه شد که یکی از مهمترین اهداف آن، ساده سازی کار با اشیاء COM و پشتیبانی از Late binding بود:
dynamic excel = Activator.CreateInstance(Type.GetTypeFromProgID("Excel.Application", true)); excel.Visible = true; Console.WriteLine("Press Enter to close Excel."); Console.ReadLine(); excel.Quit();
System.__ComObject does not contain a definition for 'Visible'
یک نکته: NET Core 3x. از Late binding پشتیبانی میکند.
روش کار با اشیاء COM در NET Core 2x.
چون NET Core 2x. از late binding اشیاء COM پشتیبانی نمیکند، میتوان در اینجا از روش قدیمیتر کار با اشیاء COM که استفادهی از «Interop assemblies» نام دارد، استفاده کرد. Interop assemblies در حقیقت محصور کنندههای اشیاء COM هستند که امکان کار مستقیم با آنها را از طریق early binding میسر میکنند. در یک چنین حالتی، کدهای فوق برای دسترسی به اشیاء COM کار با اکسل، به صورت زیر که early binding نام دارد، تغییر میکند:
using Excel = Microsoft.Office.Interop.Excel; // ... var excel = new Excel.Application(); excel.Visible = true; Console.WriteLine("Press Enter to close Excel."); Console.ReadLine(); excel.Quit();
روش تولید Interop assemblies
هنوز خود NET Core. روشی را برای تولید Interop assemblies ارائه ندادهاست و تولید آنها یکی از معدود مواردی است که نیاز به نصب Visual Studio را دارد. برای این منظور یک پروژهی خالی (از هر نوعی) را که بر اساس NET Framework 4x. تهیه میشود، در VS آغاز کنید و سپس در solution explorer بر روی پروژهی ایجاد شده کلیک راست کرده و گزینهی Add > Reference را انتخاب کنید. در صفحهی باز شده، گزینهی COM آنرا باید انتخاب کنید. در اینجا است که میتوانید با انتخاب یکی از موارد، ارجاعی را به آن شیء COM اضافه کنید.
پس از اینکار:
- ابتدا این ارجاع اضافه شده را در solution explorer انتخاب کرده و در پایین صفحه، در قسمت برگهی خواص آن، گزینهی «Embed Interop Types» آنرا به false تنظیم کنید.
- سپس یکبار پروژه را نیز کامپایل کنید.
این مراحل سبب تولید یک فایل dll خواهند شد که Interop assembly نام دارد و هم در برنامههای NET. و هم NET Core.، قابل استفادهاست.
روش استفاده از Interop assemblies در برنامههای NET Core.
اکنون که یک فایل dll را از شیء COM انتخابی، در یک پروژهی مجزای مبتنی بر NET 4x. تولید کردیم، روش استفادهی از آن در یک برنامهی دیگر مبتنی بر NET Core. به صورت زیر است:
<ItemGroup> <Reference Include="Interop.WIA"> <HintPath>..\DNTScanner.Core.TypeLibrary\bin\Debug\Interop.WIA.dll</HintPath> <EmbedInteropTypes>True</EmbedInteropTypes> </Reference> </ItemGroup>
یک نکته: اگر EmbedInteropTypes را به true تنظیم کردید، نیاز به بستهی Microsoft.CSharp را نیز خواهید داشت:
<ItemGroup Condition=" '$(TargetFramework)' == 'net40' "> <Reference Include="Microsoft.CSharp" /> </ItemGroup> <ItemGroup Condition="'$(TargetFramework)' == 'netstandard2.0'"> <PackageReference Include="Microsoft.CSharp" Version="4.5.0" /> </ItemGroup>
روش دیگر استفاده از Interop assemblies در برنامههای NET Core.
روش فوق، جهت کار با فایلهای dll ای است که خودمان تولید کردهایم. برای سایر حالاتی که این موارد در سیستم نصب شدهاند (مانند Office Primary Interop Assemblies (PIA))، پس از افزودن ارجاعی به COM reference مدنظر، فایل csproj همان پروژهی NET 4x. را باز کرده و قسمت COMReference آنرا در اینجا (در فایل csproj پروژهی NET Core.) کپی کنید:
<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <OutputType>Exe</OutputType> <TargetFramework>netcoreapp3.0</TargetFramework> </PropertyGroup> <!-- The following 'COMReference' items were copied from a .NET Framework project. They were added by using the Visual Studio COM References window. See https://docs.microsoft.com/en-us/visualstudio/ide/managing-references-in-a-project?view=vs-2017. Observe the 'EmbedInteropTypes' tag value. See https://docs.microsoft.com/en-us/visualstudio/msbuild/common-msbuild-project-items?view=vs-2017#comreference --> <ItemGroup> <COMReference Include="Microsoft.Office.Core"> <Guid>{2DF8D04C-5BFA-101B-BDE5-00AA0044DE52}</Guid> <VersionMajor>2</VersionMajor> <VersionMinor>8</VersionMinor> <Lcid>0</Lcid> <WrapperTool>primary</WrapperTool> <Isolated>False</Isolated> <EmbedInteropTypes>True</EmbedInteropTypes> </COMReference> <COMReference Include="Microsoft.Office.Interop.Excel"> <Guid>{00020813-0000-0000-C000-000000000046}</Guid> <VersionMajor>1</VersionMajor> <VersionMinor>9</VersionMinor> <Lcid>0</Lcid> <WrapperTool>primary</WrapperTool> <Isolated>False</Isolated> <EmbedInteropTypes>True</EmbedInteropTypes> </COMReference> <COMReference Include="VBIDE"> <Guid>{0002E157-0000-0000-C000-000000000046}</Guid> <VersionMajor>5</VersionMajor> <VersionMinor>3</VersionMinor> <Lcid>0</Lcid> <WrapperTool>primary</WrapperTool> <Isolated>False</Isolated> <EmbedInteropTypes>True</EmbedInteropTypes> </COMReference> </ItemGroup> </Project>
سپس یک نمونه از MS Office automation را توسط اشیاء COM آن به صورت زیر میتوان پیاده سازی کرد:
using System; using System.Reflection; using Excel = Microsoft.Office.Interop.Excel; namespace ExcelDemo { class Program { public static void Main(string[] args) { Excel.Application excel; Excel.Workbook workbook; Excel.Worksheet sheet; Excel.Range range; try { // Start Excel and get Application object. excel = new Excel.Application(); excel.Visible = true; // Get a new workbook. workbook = excel.Workbooks.Add(Missing.Value); sheet = (Excel.Worksheet)workbook.ActiveSheet; // Add table headers going cell by cell. sheet.Cells[1, 1] = "First Name"; sheet.Cells[1, 2] = "Last Name"; sheet.Cells[1, 3] = "Full Name"; sheet.Cells[1, 4] = "Salary"; // Format A1:D1 as bold, vertical alignment = center. sheet.get_Range("A1", "D1").Font.Bold = true; sheet.get_Range("A1", "D1").VerticalAlignment = Excel.XlVAlign.xlVAlignCenter; // Create an array to multiple values at once. string[,] saNames = new string[5, 2]; saNames[0, 0] = "John"; saNames[0, 1] = "Smith"; saNames[1, 0] = "Tom"; saNames[1, 1] = "Brown"; saNames[2, 0] = "Sue"; saNames[2, 1] = "Thomas"; saNames[3, 0] = "Jane"; saNames[3, 1] = "Jones"; saNames[4, 0] = "Adam"; saNames[4, 1] = "Johnson"; // Fill A2:B6 with an array of values (First and Last Names). sheet.get_Range("A2", "B6").Value2 = saNames; // Fill C2:C6 with a relative formula (=A2 & " " & B2). range = sheet.get_Range("C2", "C6"); range.Formula = "=A2 & \" \" & B2"; // Fill D2:D6 with a formula(=RAND()*100000) and apply format. range = sheet.get_Range("D2", "D6"); range.Formula = "=RAND()*100000"; range.NumberFormat = "$0.00"; // AutoFit columns A:D. range = sheet.get_Range("A1", "D1"); range.EntireColumn.AutoFit(); // Make sure Excel is visible and give the user control // of Microsoft Excel's lifetime. excel.Visible = true; excel.UserControl = true; } catch (Exception e) { Console.WriteLine($"Error: {e.Message} Line: {e.Source}"); } } } }
How to automate Microsoft Excel from Microsoft Visual C#.NET
در SharePoint 2010 گزینه دیگری در برنامه نویسی، برای دسترسی به دادههای SharePoint تدارک دیده شده است: Client Object Model. این یک روش جدید، در برنامه نویسی شیرپوینت است. اگرچه استفاده از web services، پوشش وسیعی از امکانات شیرپوینت را به شما میدهد، اما برنامه نویسی به روش Client Object Model و API با استفاده از web services بسیار متفاوت است. استفاده از web services کار را برای شما سخت خواهد کرد و لازم است دو روش برنامه نویسی کاملا مختلف را بیاموزید. همچنین فراخوانی web services با JavaScript پیچیده است و نیازمند ساخت و دستکاری XMLهای فراوان است. Client Object Model تمام این مسائل را حل و برنامه نویسی سمت client را راحت کرده است.
در واقع Client Object Model سه Object Model جدا از هم است:
نسخه: .NET CLR برای ساخت WinForms, Windows Presentation Foundation (WPF), console applications
نسخه Silverlight : برای کا با هر دو حالت داخل in-browser و out-of-browser Silverlight applications
نسخه JavaScript : کدهای Ajax و jQuery را قادر میسازد تا دادههای شیرپوینت را فراخوانی کنند
یکی از سوالاتی که در مورد Client Object Model پیش میآید، این است که چه کارهایی را با آن میشود انجام داد؟ Client Object Model امکان دسترسی به بیشتر اشیاء رایج را مانند sites, webs, content types, lists, folders, navigations فراهم میکند. این اشیا با اسمهای مشابه در Client Object Model وجود دارند که در جدول زیر مشخص شدهاند.
در زیر یک مثال ساده از استفادههای Client Object Model را توضیح خواهم داد که لیستهای موجود در سایت را در خروجی نمایش میدهد.
1- در Visual Studio یک پروژه Console application ایجاد کنید.
2- بر روی References کلیک راست کرده Add Reference را انتخاب کنید. از مسیر زیر
C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\14\ISAPI
Microsoft.SharePoint.dll Microsoft.SharePoint.Client.Runtime.dll
static void Main(string[] args) { var ctx = new ClientContext(@"http://localhost"); var web = ctx.Web; var lists = web.Lists; ctx.Load(lists, l => l.Include (list => list.Title).Where (list => list.BaseType == BaseType.GenericList)); ctx.ExecuteQuery(); foreach (var list in lists) Console.WriteLine(list.Title); Console.ReadLine(); }
پیشنیازهای کار با کامپوننت ng2-file-upload
برای شروع به کار با کامپوننت ng2-file-upload، ابتدا نیاز است بستهی npm آنرا نصب کرد:
>npm install ng2-file-upload --save
همچنین یک کامپوننت آزمایشی را هم به برنامه (دقیقا همان مثال مطلب قبلی) جهت اعمال آن اضافه میکنیم:
>ng g c UploadFile/ng2-file-upload-test
پس از آن نیاز است به ماژولی که این کامپوننت جدید در آن قرار دارد، مدخل FileUploadModule کامپوننت ng2-file-upload را افزود:
import { FileUploadModule } from "ng2-file-upload"; @NgModule({ imports: [ FileUploadModule ]
تکمیل Ng2FileUploadTestComponent جهت اعمال ng2-file-upload
اکنون به کلاس کامپوننت جدیدی که ایجاد کردیم، مراجعه کرده و تغییرات ذیل را اعمال میکنیم:
import { FileUploader, FileUploaderOptions } from "ng2-file-upload"; import { Ticket } from "./../ticket"; export class Ng2FileUploadTestComponent implements OnInit { fileUploader: FileUploader; model = new Ticket();
وهله سازی از کامپوننت ng2-file-upload و انجام تنظیمات اولیهی آن
پس از تعریف خاصیت عمومی fileUploader، اکنون نوبت به وهله سازی آن است:
this.fileUploader = new FileUploader( <FileUploaderOptions>{ url: "api/SimpleUpload/SaveTicket", headers: [ { name: "X-XSRF-TOKEN", value: this.getCookie("XSRF-TOKEN") }, { name: "Accept", value: "application/json" } ], isHTML5: true, // allowedMimeType: ["image/jpeg", "image/png", "application/pdf", "application/msword", "application/zip"] allowedFileType: [ "application", "image", "video", "audio", "pdf", "compress", "doc", "xls", "ppt" ], removeAfterUpload: true, autoUpload: false, maxFileSize: 10 * 1024 * 1024 } );
- اگر برنامه از نکات anti-forgery token استفاده میکند، این کامپوننت برخلاف روش مطرح شدهی در مطلب مشابه قبلی، هیچ هدری را به سمت سرور ارسال نمیکند. بنابراین نیاز است کوکی مرتبط را خودمان یافته و سپس به لیست هدرها اضافه کنیم. در اینجا روش استخراج یک کوکی را توسط کدهای جاوا اسکریپتی مشاهده میکنید:
getCookie(name: string): string { const value = "; " + document.cookie; const parts = value.split("; " + name + "="); if (parts.length === 2) { return decodeURIComponent(parts.pop().split(";").shift()); } }
- برای محدود سازی فایلهای ارسالی توسط این کامپوننت، دو روش وجود دارد:
الف) مشخص سازی مقدار خاصیت allowedMimeType
همانطور که مشاهده میکنید، در اینجا باید mime type فایلهای مجاز را مشخص کرد.
ب) مشخص سازی مقدار خاصیت allowedFileType
برخلاف تصور، در اینجا از پسوند فایلها استفاده نمیکند و از یک لیست از پیش مشخص که نمونهای از آنرا در اینجا مشاهده میکنید، کمک گرفته میشود. بنابراین اگر برای مثال تنها نیاز به ارسال تصاویر بود، مقدار image را نگه داشته و مابقی را از لیست حذف کنید.
- removeAfterUpload به این معنا است که آیا لیست نهایی که نمایش داده میشود، پس از آپلود باقی بماند یا خیر؟
- توسط خاصیت maxFileSize میتوان حداکثر اندازهی قابل قبول فایلهای ارسالی را مشخص کرد.
مدیریت رخدادهای کامپوننت ng2-file-upload
اکنون که وهلهای از این کامپوننت ساخته شدهاست، میتوان رخدادهای آنرا نیز مدیریت کرد. برای مثال:
الف) نحوهی ارسال اطلاعات اضافی به همراه یک فایل به سمت سرور
this.fileUploader.onBuildItemForm = (fileItem, form) => { for (const key in this.model) { if (this.model.hasOwnProperty(key)) { form.append(key, this.model[key]); } } };
ب) اطلاع یافتن از رخداد خاتمهی کار
رخداد onCompleteAll پس از ارسال تمام فایلها به سمت سرور فراخوانی میشود:
this.fileUploader.onCompleteAll = () => { // clear the form // this.model = new Ticket(); };
ج) در حین وهله سازی fileUploader، تعدادی محدودیت نیز قابل اعمال هستند. این محدودیتها سبب نمایش هیچگونه پیام خطایی نمیشوند. فقط زمانیکه کاربر فایلی را انتخاب میکند، این فایل در لیست ظاهر نمیشود. اگر علاقمند به مدیریت این وضعیت باشید، میتوان از رخداد onWhenAddingFileFailed استفاده کرد:
this.fileUploader.onWhenAddingFileFailed = (item, filter, options) => { // msg: `You can't select ${item.name} file because of the ${filter.name} filter.` };
د) اگر ارسال فایلی به سمت سرور با شکست مواجه شود، در رخدادگردان onErrorItem میتوان به نام این فایل و اطلاعات بیشتری که از سمت سرور دریافت شدهاست، دسترسی یافت:
this.fileUploader.onErrorItem = (fileItem, response, status, headers) => { // };
ه) اگر از سمت سرور اطلاعات JSON مانندی یا هر اطلاعات دیگری به سمت کلاینت پس از آپلود ارسال میشود، این اطلاعات را میتوان در رخدادگردان onSuccessItem دریافت کرد:
this.fileUploader.onSuccessItem = (item, response, status, headers) => { if (response) { const ticket = JSON.parse(response); console.log(`ticket:`, ticket); } };
ارسال نهایی فرم و فایلها به سمت سرور
در پایان، با فراخوانی متد uploadAll شیء fileUploader جاری، میتوان اطلاعات فرم و تمام فایلهای آنرا به سمت سرور ارسال کرد:
submitForm(form: NgForm) { this.fileUploader.uploadAll(); // NOTE: Upload multiple files in one request -> https://github.com/valor-software/ng2-file-upload/issues/671 }
کدهای کامل کامپوننت ng2-file-upload-test.component.ts را در اینجا میتوانید مشاهده کنید.
تکمیل قالب کامپوننت Ng2FileUploadTestComponent
اکنون که کار تکمیل کامپوننت آزمایشی ارسال فایلها به سمت سرور به پایان رسید، نوبت به تکمیل قالب آن است.
افزودن فیلد اضافی توضیحات به فرم
<div class="container"> <h3>Support Form(ng2-file-upload)</h3> <form #form="ngForm" (submit)="submitForm(form)" novalidate> <div class="form-group" [class.has-error]="description.invalid && description.touched"> <label class="control-label">Description</label> <input #description="ngModel" required type="text" class="form-control" name="description" [(ngModel)]="model.description"> <div *ngIf="description.invalid && description.touched"> <div class="alert alert-danger" *ngIf="description.errors.required"> description is required. </div> </div> </div>
تعریف ویژهی فیلد ارسال فایلها به سمت سرور
<div class="form-group"> <label class="control-label">Screenshot(s)</label> <input required type="file" multiple ng2FileSelect [uploader]="fileUploader" class="form-control" name="screenshot"> </div>
ذکر ویژگی استاندارد multiple را نیز در اینجا مشاهده میکنید. وجود آن سبب خواهد شد تا کاربر بتواند چندین فایل را با هم انتخاب کند. اگر نیازی به ارسال چندین فایل نیست، این ویژگی را حذف کنید.
نمایش لیست فایلها و نمایش درصد پیشرفت آپلود آنها
جدولی را که در تصویر ابتدای بحث مشاهده کردید، به صورت ذیل شکل میگیرد (کدهای آن در همان صفحهی توضیحات کامپوننت نیز موجود هستند):
<div style="margin-bottom: 10px" *ngIf="fileUploader.queue.length"> <h3>Upload queue</h3> <p>Queue length: {{ fileUploader?.queue?.length }}</p> <table class="table"> <thead> <tr> <th width="50%">Name</th> <th>Size</th> <th>Progress</th> <th>Status</th> <th>Actions</th> </tr> </thead> <tbody> <tr *ngFor="let item of fileUploader.queue"> <td><strong>{{ item?.file?.name }}</strong></td> <td nowrap>{{ item?.file?.size/1024/1024 | number:'.2' }} MB</td> <td> <div class="progress" style="margin-bottom: 0;"> <div class="progress-bar" role="progressbar" [ngStyle]="{ 'width': item.progress + '%' }"></div> </div> </td> <td class="text-center"> <span *ngIf="item.isError"><i class="glyphicon glyphicon-remove"></i></span> </td> <td nowrap> <button type="button" class="btn btn-danger btn-xs" (click)="item.remove()"> <span class="glyphicon glyphicon-trash"></span> Remove </button> </td> </tr> </tbody> </table> <div> <div> Queue progress: <div class="progress"> <div class="progress-bar" role="progressbar" [ngStyle]="{ 'width': fileUploader.progress + '%' }"></div> </div> </div> <button type="button" class="btn btn-danger btn-s" (click)="fileUploader.clearQueue()" [disabled]="!fileUploader.queue.length"> <span class="glyphicon glyphicon-trash"></span> Remove all </button> </div> </div>
در اینجا از progress-bar بوت استرپ برای نمایش درصد آپلود فایلها استفاده شدهاست:
<div class="progress" style="margin-bottom: 0;"> <div class="progress-bar" role="progressbar" [ngStyle]="{ 'width': item.progress + '%' }"></div> </div>
غیرفعال کردن دکمهی ارسال، در صورت عدم انتخاب یک فایل
<button class="btn btn-primary" [disabled]="form.invalid || !fileUploader.queue.length" type="submit">Submit</button> </form>
کدهای کامل این قسمت را از اینجا میتوانید دریافت کنید.
آماده سازی یک مثال Self host
برای اینکه خروجیهای JSON را بهتر و بدون نیاز به ابزار خاصی مشاهده کنیم، میتوان یک پروژهی کنسول جدید را آغاز کرده و سپس آنرا تبدیل به Host مخصوص Web API کرد. برای اینکار تنها کافی است در کنسول پاور شل نیوگت دستور ذیل را صادر کنید:
PM> Install-Package Microsoft.AspNet.WebApi.OwinSelfHost
using System; using System.Collections.Generic; using System.Net; using System.Net.Http; using System.Threading.Tasks; using System.Web.Http; namespace WebApiSelfHostTests { public class UsersController : ApiController { public IEnumerable<User> GetAllUsers() { return new[] { new User{ Id = 1, Name = "User 1", Type = UserType.Admin }, new User{ Id = 2, Name = "User 2", Type = UserType.User } }; } public async Task<HttpResponseMessage> Post(HttpRequestMessage request) { var jsonContent = await request.Content.ReadAsStringAsync(); Console.WriteLine("JsonContent (Server Side): {0}", jsonContent); return new HttpResponseMessage(HttpStatusCode.Created); } } }
namespace WebApiSelfHostTests { public enum UserType { User, Admin, Writer } public class User { public int Id { set; get; } public string Name { set; get; } public UserType Type { set; get; } } }
using System.Web.Http; using Newtonsoft.Json; using Newtonsoft.Json.Converters; using Owin; namespace WebApiSelfHostTests { /// <summary> /// PM> Install-Package Microsoft.AspNet.WebApi.OwinSelfHost /// </summary> public class Startup { public void Configuration(IAppBuilder appBuilder) { var config = new HttpConfiguration(); config.Routes.MapHttpRoute( name: "DefaultApi", routeTemplate: "api/{controller}/{id}", defaults: new { id = RouteParameter.Optional } ); appBuilder.UseWebApi(config); } } }
var server = WebApp.Start<Startup>(url: BaseAddress); Console.WriteLine("Press Enter to quit."); Console.ReadLine(); server.Dispose();
using (var client = new HttpClient()) { var response = client.GetAsync(BaseAddress + "api/users").Result; Console.WriteLine("Response: {0}", response); Console.WriteLine("JsonContent (Client Side): {0}", response.Content.ReadAsStringAsync().Result); }
JsonContent (Client Side): [{"Id":1,"Name":"User 1","Type":1},{"Id":2,"Name":"User 2","Type":0}]
تنظیمات JSON سمت سرور Web API
برای تغییر این خروجی، در سمت سرور تنها کافی است به کلاس Startup مراجعه و HttpConfiguration را به صورت ذیل تنظیم کنیم:
public class Startup { public void Configuration(IAppBuilder appBuilder) { var config = new HttpConfiguration(); config.Formatters.JsonFormatter.SerializerSettings = new JsonSerializerSettings { Converters = { new StringEnumConverter() } };
اینبار اگر برنامه را اجرا کنیم، چنین خروجی حاصل میگردد و در آن دیگر Type مساوی صفر نیست:
JsonContent (Client Side): [{"Id":1,"Name":"User 1","Type":"Admin"},{"Id":2,"Name":"User 2","Type":"User"}]
تنظیمات JSON سمت کلاینت Web API
اکنون در سمت کلاینت قصد داریم اطلاعات یک کاربر را با فرمت JSON به سمت سرور ارسال کنیم. روش متداول آن توسط کتابخانهی HttpClient، استفاده از متد PostAsJsonAsync است:
var user = new User { Id = 1, Name = "User 1", Type = UserType.Writer }; var client = new HttpClient(); client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); var response = client.PostAsJsonAsync(BaseAddress + "api/users", user).Result; Console.WriteLine("Response: {0}", response);
JsonContent (Server Side): {"Id":1,"Name":"User 1","Type":2}
var jsonMediaTypeFormatter = new JsonMediaTypeFormatter { SerializerSettings = new JsonSerializerSettings { Converters = { new StringEnumConverter() } } }; var response = client.PostAsync(BaseAddress + "api/users", user, jsonMediaTypeFormatter).Result; Console.WriteLine("Response: {0}", response);
اینبار مقدار دریافتی در سمت سرور به صورت ذیل است و در آن، Type دیگر عددی نیست:
JsonContent (Server Side): {"Id":1,"Name":"User 1","Type":"Writer"}
مثال کامل این بحث را از اینجا میتوانید دریافت کنید:
UsersController.zip
مزایای Interface ها چیست ؟
در حالت عادی ارث بری از چند کلاس به طور هم زمان امکان پذیر نیست ولی Interfaceها این مزیت را دارند که به هر تعداد که لازم است، کلاسهای مشتق شده از آنها ارث بری کنند. این موضوع یکی از مهمترین مزایای Interface میباشد. هم چنین با استفاده از Interfaceها کدها قابلیت بهتری در نگهداری، انعطاف پذیری و استفاده مجدد پیدا میکنند.
Abstract Class چیست ؟
کلاس Abstract، یکی از ابزارهای مهم OOP میباشد که نمیتوان از آنها نمونهای ساخت. به عبارتی دیگر نمیتوانیم متغیری از کلاس Abstract تعریف کنیم. یک کلاس Abstract شبیه Interface میباشد ولی با دیدی وسیعتر. این کلاسها میتواند دارای متدهای Abstract باشند که شبیه Interface فقط اعلام میشوند و باید در کلاسهای مشتق شده بازنویسی شوند. البته میتوان در این کلاسها متدهایی داشت که Abstract نیستند و احتیاجی به پیاده سازی آنها در کلاسهای مشتق شده ندارند.
باید توجه داشت که تنها متدهایی از کلاس abstract الزام به پیاده سازی دارند که صریحا کلمهی abstract در تعریف آن متد ذکر شده باشد.
در واقع همین متدها هم الزامی به پیاده سازی ندارند. یعنی میشود در subclass هم به صورت abstract ذکر شوند. البته به شرطی که subclass هم به صورت abstract تعریف شده باشد.
در ضمن کلاس abstract میتواند متدهای ساده یا غیر abstract هم داشته باشد. همانطور که میدانید متدهای غیر abstract باید بدنه داشته باشند و نیازی به پیاده سازی ندارند.
پس کلاس abstract هم میتواند متدهایی داشته باشد که باید پیاده سازی شوند و هم متدهایی داشته باشد که لازم نباشد پیاده سازی شوند.
با توجه به تعاریف ذکر شده کلاس Abstract حالتی بین کلاسهای معمولی و Interfaceها میباشد و کلاسی میباشد که غیر قطعی و ناتمام است که باید در سطح فرزندانش تکمیل شود .
مزایای کلاسهای Abstract چیست ؟
یکی از مزیتهای کلاس Abstract فراهم نمودن کلاسی پایه برای دیگر کلاسهای مشتق شده است؛ با این توضیح که متدهای آن میتوانند کد نویسی شده باشند یا خیر. از طرفی پیاده سازی تمام متدهای Abstract در کلاس مشتق شده اجباری نیست (برخلاف Interface).
تعریف سطوح دسترسی برای متدها و خصوصیتها مانند کلاسهای معمولی نیز یکی دیگر از مزیتهای این کلاسها است.
تفاوت بین کلاسهای Abstract و Interface
1- یک کلاس معمولی تنها میتواند از یک کلاس Abstract ارث بری کند ولی همان کلاس میتواند از چندین Interface ارث ببرد.
2- یک Interface فقط میتواند اعلان متدها و خصوصیتها را داشته باشد؛ اما یک کلاس Abstract علاوه بر آنها میتوانید متدها و خصوصیتهایی با کدهای کامل داشته باشد.
3- عناصر موجود در کلاس Abstract میتوانند مانند یک کلاس معمولی دارای سطح دسترسی باشند؛ ولی Interfaceها فاقد این امکان هستند.
4- وقتی شما متدی را به کلاس Abstract اضافه میکنید، به طور خودکار به همه زیر کلاسها اعمال میشود؛ اما در Interface اگر متدی اضافه کنید باید در تمام زیر کلاسها آن را اعمال کنید .
5- کلاسهای Abstract مانند کلاسهای معمولی میتوانند دارای فیلد و عناصر دیگری (مثل ثابتها) باشند؛ در حالیکه یک Interface فاقد این امکان میباشد. همچنین کلاس abstract میتواند شامل سازنده باشد، اما اینترفیس نمیتواند.
6- Abstract یکی از انواع کلاس است؛ ولی Interface کلاس نیست .
7- اینترفیس تنها میتواند از اینترفیس ارث بری کند اما کلاس abstract میتواند از اینترفیس، کلاس Abstract و یا سایر کلاسها ارث بری کند.
چه زمانی از Interface ها و یا کلاسهای Abstract استفاده کنیم؟
- با توجه به توضیحات ذکر شده مواقعی که نیاز به وراثت چند گانه داریم، باید از Interface استفاده کنیم؛ به دلیل اینکه این امکان در کلاسهای Abstract وجود ندارد.
- زمانی که بخواهیم تمام متدهای معرفی شده در کلاس پایه به طور کامل در کلاس مشتق شده پیاده شوند باید از Interface استفاده کنیم.
- وقتی در پروژههای بزرگ با تغییرات زیادی مواجه هستیم، استفاده از کلاس Abstract توصیه میشود؛ چون با تغییر آن به طور خودکار تغییرات در کلاسهای مشتق شده اعمال میشوند.
- با توجه به اینکه به غیر از اعلان متدها و خصوصیتها امکان تعریف عناصر دیگری در Interfaceها وجود ندارد، در صورتیکه ملزم به استفاده از این عناصر باشیم، استفاده از کلاسهای Abstract ضروری میباشد.
- در صورتی که نخواهیم کلیه متدها در کلاسهای مشتق شده پیاده سازی شوند و تعدادی از آنها را در کلاس پدر کدنویسی کنیم، باید از کلاس Abstract استفاده کنیم.- به طور کلی یک Interface چارچوب و قابلیتهای یک کلاس را مشخص میکند و یک قرارداد است؛ ولی کلاس Abstract نوع کلاس را معین میکند. این تفاوت کمک بسیاری برای تشخیص زمان استفاده از این دو را به برنامه نویسان میدهد.
public class TestViewModel { private readonly ITestService _testService; public TestViewModel(ITestService testService) //تزریق وابستگی در سازنده کلاس { _testService = testService; }
نام تمام Viewهای برنامه به View ختم میشوند و نام ViewModelها به ViewModel. برای مثال TestViewModel و TestView معرف یک ViewModel و View متناظر خواهند بود.
ساختار کلاسهای لایه سرویس برنامه
namespace DI07.Services { public interface ITestService { string Test(); } } namespace DI07.Services { public class TestService: ITestService { public string Test() { return "برای آزمایش"; } } }
علامتگذاری ViewModelها
در ادامه یک اینترفیس خالی را به نام IViewModel مشاهده میکنید:
namespace DI07.Core { public interface IViewModel // از این اینترفیس خالی برای یافتن و علامتگذاری ویوو مدلها استفاده میکنیم { } }
برای نمونه کلاس TestViewModel برنامه، با پیاده سازی IViewModel، به نوعی نشانه گذاری نیز شده است:
using DI07.Services; using DI07.Core; namespace DI07.ViewModels { public class TestViewModel : IViewModel // علامتگذاری ویوو مدل { private readonly ITestService _testService; public TestViewModel(ITestService testService) //تزریق وابستگی در سازنده کلاس { _testService = testService; } public string Data { get { return _testService.Test(); } } } }
تنظیمات آغازین IoC Container مورد استفاده
در کلاس استاندارد App برنامه WPF خود، کار تنظیمات اولیه StructureMap را انجام خواهیم داد:
using System.Windows; using DI07.Core; using DI07.Services; using StructureMap; namespace DI07 { public partial class App { protected override void OnStartup(StartupEventArgs e) { base.OnStartup(e); ObjectFactory.Configure(cfg => { cfg.For<ITestService>().Use<TestService>(); cfg.Scan(scan => { scan.TheCallingAssembly(); // Add all types that implement IView into the container, // and name each specific type by the short type name. scan.AddAllTypesOf<IViewModel>().NameBy(type => type.Name); scan.WithDefaultConventions(); }); }); } } }
همچنین در ادامه از قابلیت اسکن این IoC Container برای یافتن کلاسهایی که IViewModel را در اسمبلی جاری پیاده سازی کردهاند، استفاده شده است. متد NameBy، سبب میشود که بتوان به این نوعهای یافت شده از طریق نام کلاسهای متناظر دسترسی یافت.
اتصال خودکار ViewModelها به Viewهای برنامه
using System.Windows.Controls; using StructureMap; namespace DI07.Core { /// <summary> /// Stitches together a view and its view-model /// </summary> public static class ViewModelFactory { public static void WireUp(this ContentControl control) { var viewName = control.GetType().Name; var viewModelName = string.Concat(viewName, "Model"); //قرار داد نامگذاری ما است control.Loaded += (s, e) => { control.DataContext = ObjectFactory.GetNamedInstance<IViewModel>(viewModelName); }; } } }
استفاده از کلاس ViewModelFactory
در ادامه کدهای TestView و پنجره اصلی برنامه را مشاهده میکنید:
<UserControl x:Class="DI07.Views.TestView" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" mc:Ignorable="d" d:DesignHeight="300" d:DesignWidth="300"> <Grid> <TextBlock Text="{Binding Data}" /> </Grid> </UserControl> <Window x:Class="DI07.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:Views="clr-namespace:DI07.Views" Title="MainWindow" Height="350" Width="525"> <Grid> <Views:TestView /> </Grid> </Window>
using DI07.Core; namespace DI07.Views { public partial class TestView { public TestView() { InitializeComponent(); this.WireUp(); //تزریق خودکار وابستگیها و یافتن ویوو مدل متناظر } } }
دریافت پروژه کامل این قسمت
DI07.zip
Static Memory
حافظهی static برای ذخیرهی باینریهای برنامه، متغیرهای استاتیک و حروف رشتهای (در Rust) استفاده میشود. اندازهی حافظه استاتیک ثابت است و در زمان کامپایل مشخص میشود. حافظهی استاتیک طول عمری برابر با عمر برنامه دارد و مقادیر آن از شروع، تا پایان برنامه، باقی میماند. پاکسازی حافظهی استاتیک به صورت خودکار انجام میشود و با پایان برنامه انجام میشود.
مواردی که در حافظه استاتیک قرار میگیرند :
- Program Binary
- Static variables
- String Literals (in Rust)
Size :
Fixed ( محاسبه در زمان کامپایل )
Lifetime : برابر با طول عمر برنامه
پاکسازی : به صورت خودکار ؛ زمانی که برنامه متوقف میشود .
حافظهی پشته، مسئول نگهداری آرگومانهای تابع و متغیرهای محلی است. پشته، شامل stack frames است که برای هر فراخوانی تابع در زنجیرهای از فراخوانیهای تابع، ایجاد میشوند (به عنوان مثال، A B را فرا میخواند، B C را فرا میخواند). حافظهی پشته به اندازهی مشخصی در زمان کامپایل نیاز دارد؛ به این معنا که آرگومانها و متغیرهای درون stack frames باید اندازههای از پیش تعیین شدهای داشته باشند. اندازهی پشته، پویا است؛ اما دارای حد بالایی ثابتی است که در هنگام راه اندازی برنامه تعریف شدهاست. حافظهی پشته، دارای طول عمری برابر با طول عمر عملکرد است و هنگامیکه عملکرد، نتیجهای را بر میگرداند، پاکسازی آن خودکار است.
بیایید نگاهی به یک مثال ساده در Rust بیندازیم تا حافظهی پشته را بهتر درک کنیم:
fn add(x: i32, y: i32) -> i32 { let sum = x + y; sum } fn main() { let a = 5; let b = 3; let result = add(a, b); println!("The sum is: {}", result); }
هنگامیکه تابع add فراخوانی میشود، یک stack frames دیگر در بالای stack frames main موجود ایجاد میشود. این stack frames جدید حاوی متغیرهای محلی x، y و sum است. مقادیر a و b به عنوان آرگومان به تابع add ارسال میشوند و به ترتیب در x و y ذخیره میشوند. پس از محاسبهی مجموع، تابع add، مقداری را بر میگرداند و stack frames آن به طور خودکار از حافظهی پشته حذف میشود.
سپس تابع main، مقدار برگشتی را از تابع add دریافت میکند و به نتیجهی متغیر اختصاص مییابد. از ماکروی println! برای چاپ نتیجه استفاده میشود. پس از اتمام اجرای برنامه و بازگشت تابع اصلی، stack frames آن نیز از حافظهی پشته حذف میشود و حافظه بهطور خودکار پاک میشود.
در این مثال، میتوانید ببینید که چگونه از stack frames برای ذخیرهی آرگومانهای تابع و متغیرهای محلی در Rust استفاده میشود. اندازهی این متغیرها در زمان کامپایل مشخص میشود و طول عمر حافظهی پشته، برابر با طول عمر تابع است. هنگامیکه تابع برمیگردد، فرآیند پاکسازی آن خودکار است و قاب پشتهی مربوطه را حذف میکند.
Heap Memory
حافظهی Heap، مقادیری را ذخیره میکند که باید فراتر از طول عمر یک تابع مانند مقادیر بزرگ و مقادیر قابل دسترسی توسط رشتههای متعدد، زنده بمانند. از آنجائیکه هر رشته دارای پشتهی مخصوص به خود است، همهی آنها یک پشتهی مشترک دارند. حافظهی Heap میتواند مقادیری با اندازهی ناشناخته را در زمان کامپایل، در خود جای دهد؛ مانند رشتههای ورودی کاربر. اندازهی پشته نیز پویا است؛ با حد بالایی ثابت که در زمان راه اندازی برنامه تعیین میشود. حافظهی Heap طول عمری دارد که توسط برنامه نویس تعیین میشود و برنامه نویس تصمیم میگیرد که چه زمانی باید حافظه تخصیص داده شود. پاکسازی حافظهی هیپ به صورت دستی است و نیاز به مداخلهی برنامه نویس دارد.
در این مثال ساده، روش استفاده از حافظهی پشته نشان داده میشود:
use std::rc::Rc; #[derive(Debug)] struct LargeData { data: Vec<i32>, } impl LargeData { fn new(size: usize) -> LargeData { LargeData { data: vec![0; size], } } } fn main() { let large_data = Rc::new(LargeData::new(1_000_000)); let shared_data1 = Rc::clone(&large_data); let shared_data2 = Rc::clone(&large_data); println!("{:?}", shared_data1); println!("{:?}", shared_data2); }
سپس دو متغیر دیگر را به نامهای shared_data1 و shared_data2 ایجاد میکنیم که با استفاده از Rc::clone، یک شیء LargeData تخصیصیافتهی مشابه را به اشتراک میگذارند. این نشان میدهد که چگونه حافظهی پشته را میتوان در بین متغیرهای متعددی به اشتراک گذاشت؛ حتی فراتر از طول عمر تابع اصلی که داده را ایجاد کرده است.
در این مثال، پاکسازی حافظهی پشته به طور خودکار توسط مکانیزم شمارش مرجع Rust مدیریت میشود (در ادامهی دوره توضیح داده خواهد شد). هنگامیکه تعداد مرجع نشانگر Rc به صفر میرسد (یعنی وقتی همهی متغیرهایی که دادهها را به اشتراک میگذارند از محدوده خارج میشوند)، حافظهی تخصیص داده شده، روی پشته تخصیص داده میشود.
این مثال نشان میدهد که چگونه میتوان از حافظهی پشته برای ذخیرهی ساختارهای داده یا مقادیر بزرگی استفاده کرد که باید بیشتر از طول عمر یک تابع باشند و چگونه میتوان حافظهی پشته را بین چندین متغیر به اشتراک گذاشت.
سازماندهی برنامههای Angular توسط ماژولها
الف) چگونه از import ثانویهی Core Module در سایر ماژولها جلوگیری کنیم؟
Core Module فقط باید در AppModule برنامه import شود و نه در هیچجای دیگری. برای جلوگیری اتفاقی از این مساله میتوان سازندهای را به شکل زیر به آن اضافه کرد:
@NgModule({ imports: [CommonModule, RouterModule], exports: [], // components that are used in app.component.ts will be listed here. declarations: [], // components that are used in app.component.ts will be listed here. providers: [BrowserStorageService, AppConfigService] // global singleton services of the whole app will be listed here. }) export class CoreModule { constructor( @Optional() @SkipSelf() core: CoreModule) { if (core) { throw new Error("CoreModule should be imported ONLY in AppModule."); } } };
از همین روش برای تشخیص singleton بودن یک سرویس نیز میتوان استفاده کرد. خودش را به خودش تزریق میکنیم! اگر تزریقی صورت گرفت، یک خطا را صادر میکنیم.
ب) چگونه از وهله سازی مجدد سرویسهای تعریف شدهی در Shared Module در سایر ماژولها جلوگیری کنیم؟
هدف از قسمت providers در Shared Module تنها ارائهی سرویسهایی جهت کامپوننتهای اشتراکی آن است؛ وگرنه سرویسهای سراسری برنامه در CoreModule تعریف میشوند و این ماژول ویژه نیز تنها یکبار و آنهم در AppModule برنامه import خواهد شد. اما در مورد Shared Module اینطور نیست و اگر این ماژول در یک lazy loaded module استفاده شود، سرویسهای آن طول عمر متفاوتی را پیدا خواهند کرد (هر lazy loaded module یک injector و یک طول عمر خاص خودش را تعریف میکند).
در این حالت برای اینکه سرویسهای Shared Module فقط در AppModule وهله سازی شوند و نه در هیچجای دیگری، روش کار به صورت ذیل است:
- ابتدا آرایهی providers را از تعاریف NgModule آن حذف میکنیم.
- سپس متد ویژهای را به نام forRoot، به کلاس آن اضافه خواهیم کرد:
@NgModule({ imports: [CommonModule], declarations: [], // common and shared components/directives/pipes between more than one module and components will be listed here. exports: [CommonModule], // common and shared components/directives/pipes between more than one module and components will be listed here. /* No providers here! Since they’ll be already provided in AppModule. */ }) export class SharedModule { static forRoot(): ModuleWithProviders { // Forcing the whole app to use the returned providers from the AppModule only. return { ngModule: SharedModule, providers: [ /* All of your services here. It will hold the services needed by `itself`. */] }; } };
سایر ماژولها چون دسترسی به آرایهی حذف شدهی providers این ماژول را ندارند، دیگر نمیتوانند سرویسهای آنرا وهله سازی کنند. اما AppModule با فراخوانی ()SharedModule.forRoot در لیست import خود، تنها یکبار سبب وهله سازی سرویسهای آن میگردد.
بنابراین در اینجا AppModule باید ()SharedModule.forRoot را import کند. سایر ماژولها فقط SharedModule را import میکنند (بدون ذکر متد forRoot). به این ترتیب سرویسهای آن تنها یکبار توسط AppModule در طول عمر برنامه به اشتراک گذاشته میشوند و در این حالت تفاوتی نمیکند که SharedModule در یک lazy loaded module استفاده شدهاست یا خیر.
روش تعریف متد forRoot توسط سیستم مسیریابی Angular نیز استفاده میشود و یک الگوی پذیرفته شده در بین توسعه دهندگان Angular است. برای مثال ()RouterModule.forRoot در AppModule تعریف میشود و ()RouterModule.forChild برای سایر ماژولها.
نمونهای از AppModule ، ShardModule و CoreModule