البته برای استفاده کامل از امکانات entity6 در حالتهای Model first و Database first در vs2012 میتوانید vs2012 خود را با پکیچ زیر به روز کنید:
سلام
راستش من منوهای چند سطحی پویا را برای bootstrap navbar نوشتم. الگوریتمش را مجبور شدم به صورت بازگشتی بنویسم.اگر کسی میتونه به صورت غیر بازگشتی بگه ممنونش میشم.
این کد یه partialpage برای navbar هست که هرکسی برای bootstrap به راحتی میتونه استفاده کنه.
الان تنها مشکلم اینه که فیلدهای اضافی هم کوئری گرفته میشه.میدونم فیلدهای اضافی(بر اساس مدلی که ذکر کردم) را چگونه با استفاده از select حذف کنم اما توی viewmodel نمیدونم چه جوری children را از اطلاعات پر کنم؟
ممنون
راستش من منوهای چند سطحی پویا را برای bootstrap navbar نوشتم. الگوریتمش را مجبور شدم به صورت بازگشتی بنویسم.اگر کسی میتونه به صورت غیر بازگشتی بگه ممنونش میشم.
این کد یه partialpage برای navbar هست که هرکسی برای bootstrap به راحتی میتونه استفاده کنه.
@model IEnumerable<DomainClasses.Page> @helper ShowNavBar(IEnumerable<DomainClasses.Page> pages) { foreach (var page in pages) { if (page != null) { if (page.Children.Count == 0) { <text><li><a tabindex="-1" href="#">@page.Title</a></li></text> } if (page.Children.Count > 0 && page.Parent == null) { <text><li class="dropdown"><a class="dropdown-toggle" id="dLabel" role="button" data-toggle="dropdown" data-target="#" href="/page.html">@page.Title<b class="caret"></b></a><ul class="dropdown-menu" role="menu" aria-labelledby="dLabel"><li><a tabindex="-1" href="#">@page.Title</a></li></text> @ShowNavBar(page.Children) @:</ul></li> } if (page.Children.Count > 0 && page.Parent != null) { <text><li class="dropdown-submenu"><a tabindex="-1" href="#">@page.Title</a><ul class="dropdown-menu"></text> @ShowNavBar(page.Children) @:</ul></li> } } } } <div class="navbar" style="margin-bottom: 10px;"> <div class="navbar-inner"> <a class="brand" href="www.google.com">IT-EBOOK</a> <ul class="nav"> <li class="active"><a href="#">خانه</a></li> <li><a href="#">ورود</a></li> @ShowNavBar(Model) <li><a href="#">ارتباط با ما</a></li> </ul> <div class="input-append pull-left visible-desktop" style="margin-top: 5px;"> <input class="span6 search-input" id="Text1" type="text"> <button class="btn btn-primary" type="button">جست و جو</button> <button class="btn btn-info btn-advanced-search" type="button">پیشرفته</button> </div> </div> </div>
الان تنها مشکلم اینه که فیلدهای اضافی هم کوئری گرفته میشه.میدونم فیلدهای اضافی(بر اساس مدلی که ذکر کردم) را چگونه با استفاده از select حذف کنم اما توی viewmodel نمیدونم چه جوری children را از اطلاعات پر کنم؟
ممنون
نمونهای از راه اندازی یک برنامهی Blazor WASM در IIS
الف) به قسمت application pools در IIS Manager مراجعه کرده و گزینهی add application pool را انتخاب کنید. سپس یک نمونهی جدید را برای مثال به نام no-managed ایجاد کنید که net clr version. آن به گزینهی no managed code اشاره میکند.
ب) پس از publish برنامه مطابق مطلب فوق (برای مثال با اجرای دستور dotnet publish -c Release) و معرفی مسیر پوشهی publish به IIS (برای مثال به عنوان یک Application جدید ذیل default web site با کلیک راست بر روی default web site و انتخاب گزینهی Add application)، این application pool جدید را به برنامهی خود در IIS نسبت دهید. برای اینکار basic settings سایت را باز کرده و بر روی دکمهی select که در کنار نام application pool هست، کلیک کرده و گزینهی no-managed قسمت الف را انتخاب کنید.
نکته 1: برنامههای blazor wasm، یا standalone هستند و یا hosted. مورد standalone یعنی کاری به Web API ندارد و به خودی خود، به صورت یک سایت استاتیک قابل مشاهدهاست. حالت hosted یعنی به همراه web api هم هست و توسط دستور برای مثال «dotnet new blazorwasm -o BlazorIIS --hosted --no-https» ایجاد میشود که به همراه سه پوشهی کلاینت، سرور و shared است. برای توزیع حالت متکی به خود، فقط محتویات پوشهی publish، به عنوان مسیر برنامه، در IIS معرفی خواهند شد. در حالت hosted، مسیر اصلی، پوشهی publish مربوط به پروژهی سرور است؛ یعنی: Server\bin\Release\net5.0\publish. در این حالت پوشهی Client\bin\Release\net5.0\publish باید به داخل همین پوشهی publish سرور کپی شود. یعنی پوشهی publish پروژهی client باید به درون پوشهی publish پروژهی server کپی شود تا با هم یک برنامهی قابل توزیع توسط IIS را تشکیل دهند.
ج) اکنون اگر برنامه را برای مثال با مسیر فرضی جدید http://localhost/blazortest اجرا کنید، خطای 500.19 را دریافت میکنید. علت آنرا در مطلب «بررسی خطاهای ممکن در حین راه اندازی اولیه برنامههای ASP.NET Core در IIS» بررسی کردهایم. باید IIS URL Rewrite ماژول را نصب کرد؛ تا این مشکل برطرف شود. همچنین دلیل دیگر مشاهدهی این خطا، عدم نصب بستهی هاستینگ متناظر با شماره نگارش NET. مورد استفادهاست (اگر برنامهی شما از نوع hosted است و web api هم دارد).
د) پس از آن باز هم برنامه اجرا نمیشود! اگر در خطاها دقت کنید، به دنبال اسکریپتهایی شروع شده از مسیر localhost است و نه از پوشهی جدید blazortest. برای رفع این مشکل باید فایل publish\wwwroot\index.html را مطابق نکتهی base-URL که کمی بالاتر ذکر شد، ویرایش کرد تا blazor بداند که فایلها، در چه مسیری قرار دارند (و در ریشهی سایت واقع نشدهاند):
اکنون برنامه بدون مشکل بارگذاری و اجرا میشود.
الف) به قسمت application pools در IIS Manager مراجعه کرده و گزینهی add application pool را انتخاب کنید. سپس یک نمونهی جدید را برای مثال به نام no-managed ایجاد کنید که net clr version. آن به گزینهی no managed code اشاره میکند.
ب) پس از publish برنامه مطابق مطلب فوق (برای مثال با اجرای دستور dotnet publish -c Release) و معرفی مسیر پوشهی publish به IIS (برای مثال به عنوان یک Application جدید ذیل default web site با کلیک راست بر روی default web site و انتخاب گزینهی Add application)، این application pool جدید را به برنامهی خود در IIS نسبت دهید. برای اینکار basic settings سایت را باز کرده و بر روی دکمهی select که در کنار نام application pool هست، کلیک کرده و گزینهی no-managed قسمت الف را انتخاب کنید.
نکته 1: برنامههای blazor wasm، یا standalone هستند و یا hosted. مورد standalone یعنی کاری به Web API ندارد و به خودی خود، به صورت یک سایت استاتیک قابل مشاهدهاست. حالت hosted یعنی به همراه web api هم هست و توسط دستور برای مثال «dotnet new blazorwasm -o BlazorIIS --hosted --no-https» ایجاد میشود که به همراه سه پوشهی کلاینت، سرور و shared است. برای توزیع حالت متکی به خود، فقط محتویات پوشهی publish، به عنوان مسیر برنامه، در IIS معرفی خواهند شد. در حالت hosted، مسیر اصلی، پوشهی publish مربوط به پروژهی سرور است؛ یعنی: Server\bin\Release\net5.0\publish. در این حالت پوشهی Client\bin\Release\net5.0\publish باید به داخل همین پوشهی publish سرور کپی شود. یعنی پوشهی publish پروژهی client باید به درون پوشهی publish پروژهی server کپی شود تا با هم یک برنامهی قابل توزیع توسط IIS را تشکیل دهند.
در اینجا تنها فایلهایی که تداخل پیدا میکنند، فایلهای web.config هستند که باید یکی شوند. فایل web.config برنامهی web api با برنامهی client یکی نیست، اما میتوان محتویات این دو را با هم یکی کرد.
نکته 2: محل اجرای دستور dotnet publish -c Release مهم است. اگر این دستور را در کنار فایل sln پروژهی hosted اجرا کنید، سه خروجی نهایی publish را تولید میکند و پس از آن باید فایلهای کلاینت را به سرور، به صورت دستی کپی کرد. اما اگر دستور publish را درون پوشهی سرور اجرا کنید، کار کپی فایلهای کلاینت را به درون پوشهی نهایی publish تشکیل شده، به صورت خودکار انجام میدهد؛ علت اینجا است که اگر به فایل csproj. پروژهی سرور دقت کنید، ارجاعی را به پروژهی کلاینت نیز دارد (هر چند ما از کدهای آن در برنامهی web api استفاده نمیکنیم). این ارجاع تنها کمک حال دستور dotnet publish -c Release است و کاربرد دیگری را ندارد.
ج) اکنون اگر برنامه را برای مثال با مسیر فرضی جدید http://localhost/blazortest اجرا کنید، خطای 500.19 را دریافت میکنید. علت آنرا در مطلب «بررسی خطاهای ممکن در حین راه اندازی اولیه برنامههای ASP.NET Core در IIS» بررسی کردهایم. باید IIS URL Rewrite ماژول را نصب کرد؛ تا این مشکل برطرف شود. همچنین دلیل دیگر مشاهدهی این خطا، عدم نصب بستهی هاستینگ متناظر با شماره نگارش NET. مورد استفادهاست (اگر برنامهی شما از نوع hosted است و web api هم دارد).
د) پس از آن باز هم برنامه اجرا نمیشود! اگر در خطاها دقت کنید، به دنبال اسکریپتهایی شروع شده از مسیر localhost است و نه از پوشهی جدید blazortest. برای رفع این مشکل باید فایل publish\wwwroot\index.html را مطابق نکتهی base-URL که کمی بالاتر ذکر شد، ویرایش کرد تا blazor بداند که فایلها، در چه مسیری قرار دارند (و در ریشهی سایت واقع نشدهاند):
<head> <base href="/blazortest/" />
پیش از هرچیز به شما پیشنهاد میکنم؛ بار دیگر کد سیشارپ درس نخست را در پروژهی خود کپی کنید و سپس Publish را بزنید. پس از ارسال آن مطلب، تغییراتی در جهت بهینهسازی کد دادم که به نظرم بهتر است شما نیز در پروژهی خود به کار برید.
چرا از این نوع داده استفاده کنیم؟
نخستین پرسشی که ممکن است برای شما پیش بیاید این است که چرا بهتر است از این نوع داده استفاده کنیم. برای پاسخ به این پرسش باید راهکارهای گذشته را بررسی کنیم. معمولاً طراحان پایگاه دادهها برای استفاده از تاریخ خورشیدی، زمان را به صورت میلادی ثبت میکنند؛ سپس با یک scalar-valued function زمان درج شده را به خورشیدی تبدیل میکنند. در این صورت میتوان با یک تابع کوچک دیگر بخش مربوط به ساعت را نیز از همان ستون به دست آورد. در این صورت میتوانیم از کلیهی متدهای مربوط به DateTime در SQL از جمله افزایش و کاهش و تفاضل دو تاریخ بهره برد. برخی دیگر از طراحان، ستونی از نوع (char(10 در نظر میگیرند و تاریخ خورشیدی را به صورت دهکاراکتری در آن ذخیره میکنند. این روش هرچند نیاز به تبدیل به خورشیدی را ندارد ولی کلیهی مزایایی که در استفاده از DateTime به آنها دسترسی داریم از دست میدهیم. افزون بر این جهت نگهداری زمان باید یک فیلد دیگر از نوع کاراکتری و یا در نگارشهای نوینتر از نوع time تعریف کنیم. برخی دیگر از هر دو را در کنار هم استفاده میکنند و در واقع جهت سرعت بالاتر نمایش و بررسی دادهها از طریق محیط SQL Server از فیلد کاراکتری تاریخ خورشیدی و برای مقایسه و بدست آوردن ساعت از فیلد نوع DateTime استفاده میکنند.
از نظر فضای اشغالشده نوع DataTime، هشت بایت، smalldatetime (در صورت استفاده) 4 بایت و فیلد 10 کاراکتری تاریخ 10 بایت فضا اشغال میکند در صورتی که نوع JalaliDate با درنظر گرفتن همهی مزایای انواع دادهی استفادهشده برای تاریخ، فقط 8 بایت فضا اشغال میکند. با استفاده از این نوع به راحتی دادهی تاریخ را بر اساس تقویم ایرانی اعتبارسنجی میکنید و بخشهای مختلف زمان از سال تا ثانیه را با یک متد به دست میآورید. میتوانید به راحتی به تاریخ خود زمانی را بیفزایید یا بکاهید و در گزارشها بدون نگرانی از تبدیل درست استفاده کنید. چون کدباز است میتوانید با کمی حوصله امکانات دیگر مد نظر خود را به آن بیفزایید و از آن در SQL بهره ببرید.
چگونه این نوع داده را حذف کنم!؟
شما میتوانید به سادگی نوع دادهی ایجادشده توسط CLR را در مسیر زیر بیابید و اقدام به حذف آن نمایید:
همانطور که مشاهده میشود؛ حتی نوع دادهی سیستمی hierarchyid که جهت ساختار سلسلهمراتبی مانند چارت سازمانی یا درخت تجهیزات استفاده میشود؛ نیز یک نوع دادهی CLR است.
آیا راه دیگری نیز برای افزودن این نوع داده به SQL به جز Publish کردن وجود دارد؟
مانند بسیاری دیگر از گونههای پروژه، در اینجا نیز شما یک فایل DLL خواهید داشت. این فایل برپایهی تنظیماتی که شما در قسمت Properties پروژهی خود انجام میدهید ساخته میشود. پس از تغییر مسیر فایل DLL در دستور زیر توسط یک New Query از Database خود، آن را اجرا کنید:
همچنین در صورت ویرایشهای دوباره پروژه از دستور زیر استفاده کنید:
با استفاده از دستورهای زیر میتوانید از چگونگی درج فایلهای افزوده شده آگاه شوید:
تا اینجا SQL Server، دیالال مربوط به پروژه را شناخته است. برای تعریف نوع داده از دستور زیر بهره ببرید:
این کار همانند استفاده از گزینهی Publish در Visual Studio است.
همچنین چنانچه در SQL Server 2012 از منوی راستکلیک پایگاه دادهها روی گزینه Tasks و سپس Generate Scripts را انتخاب کنیم، از مشاهدهی سند ساخته شده، درخواهیم یافت که حتی دستورهای مربوط به ساخت اسمبلی CLR با تبدیل فایل به کد در Scripts وجود دارد و با اجرای آن در سروری دیگر، انتقال مییابد.
دنباله دارد ...
چرا از این نوع داده استفاده کنیم؟
نخستین پرسشی که ممکن است برای شما پیش بیاید این است که چرا بهتر است از این نوع داده استفاده کنیم. برای پاسخ به این پرسش باید راهکارهای گذشته را بررسی کنیم. معمولاً طراحان پایگاه دادهها برای استفاده از تاریخ خورشیدی، زمان را به صورت میلادی ثبت میکنند؛ سپس با یک scalar-valued function زمان درج شده را به خورشیدی تبدیل میکنند. در این صورت میتوان با یک تابع کوچک دیگر بخش مربوط به ساعت را نیز از همان ستون به دست آورد. در این صورت میتوانیم از کلیهی متدهای مربوط به DateTime در SQL از جمله افزایش و کاهش و تفاضل دو تاریخ بهره برد. برخی دیگر از طراحان، ستونی از نوع (char(10 در نظر میگیرند و تاریخ خورشیدی را به صورت دهکاراکتری در آن ذخیره میکنند. این روش هرچند نیاز به تبدیل به خورشیدی را ندارد ولی کلیهی مزایایی که در استفاده از DateTime به آنها دسترسی داریم از دست میدهیم. افزون بر این جهت نگهداری زمان باید یک فیلد دیگر از نوع کاراکتری و یا در نگارشهای نوینتر از نوع time تعریف کنیم. برخی دیگر از هر دو را در کنار هم استفاده میکنند و در واقع جهت سرعت بالاتر نمایش و بررسی دادهها از طریق محیط SQL Server از فیلد کاراکتری تاریخ خورشیدی و برای مقایسه و بدست آوردن ساعت از فیلد نوع DateTime استفاده میکنند.
از نظر فضای اشغالشده نوع DataTime، هشت بایت، smalldatetime (در صورت استفاده) 4 بایت و فیلد 10 کاراکتری تاریخ 10 بایت فضا اشغال میکند در صورتی که نوع JalaliDate با درنظر گرفتن همهی مزایای انواع دادهی استفادهشده برای تاریخ، فقط 8 بایت فضا اشغال میکند. با استفاده از این نوع به راحتی دادهی تاریخ را بر اساس تقویم ایرانی اعتبارسنجی میکنید و بخشهای مختلف زمان از سال تا ثانیه را با یک متد به دست میآورید. میتوانید به راحتی به تاریخ خود زمانی را بیفزایید یا بکاهید و در گزارشها بدون نگرانی از تبدیل درست استفاده کنید. چون کدباز است میتوانید با کمی حوصله امکانات دیگر مد نظر خود را به آن بیفزایید و از آن در SQL بهره ببرید.
چگونه این نوع داده را حذف کنم!؟
شما میتوانید به سادگی نوع دادهی ایجادشده توسط CLR را در مسیر زیر بیابید و اقدام به حذف آن نمایید:
همانطور که مشاهده میشود؛ حتی نوع دادهی سیستمی hierarchyid که جهت ساختار سلسلهمراتبی مانند چارت سازمانی یا درخت تجهیزات استفاده میشود؛ نیز یک نوع دادهی CLR است.
آیا راه دیگری نیز برای افزودن این نوع داده به SQL به جز Publish کردن وجود دارد؟
مانند بسیاری دیگر از گونههای پروژه، در اینجا نیز شما یک فایل DLL خواهید داشت. این فایل برپایهی تنظیماتی که شما در قسمت Properties پروژهی خود انجام میدهید ساخته میشود. پس از تغییر مسیر فایل DLL در دستور زیر توسط یک New Query از Database خود، آن را اجرا کنید:
CREATE ASSEMBLY JalaliDate FROM 'F:\prgJalaliDate.dll' WITH PERMISSION_SET = SAFE;
ALTER ASSEMBLY JalaliDate FROM 'F:\prgJalaliDate.dll'
select * from sys.assemblies select * from sys.assembly_files
CREATE TYPE dbo.JalaliDate EXTERNAL NAME JalaliDate.[JalaliDate];
همچنین چنانچه در SQL Server 2012 از منوی راستکلیک پایگاه دادهها روی گزینه Tasks و سپس Generate Scripts را انتخاب کنیم، از مشاهدهی سند ساخته شده، درخواهیم یافت که حتی دستورهای مربوط به ساخت اسمبلی CLR با تبدیل فایل به کد در Scripts وجود دارد و با اجرای آن در سروری دیگر، انتقال مییابد.
GO /****** Object: SqlAssembly [prgJalaliDate] Script Date: 2013/04/30 08:27:00 ب.ظ ******/ CREATE ASSEMBLY [prgJalaliDate] FROM 0x4D5A90000300000004000000FFFF0000B8000000000000 ..... بقیهی کدها حذف شده WITH PERMISSION_SET = SAFE GO ALTER ASSEMBLY [prgJalaliDate] ADD FILE FROM 0x4D6963726F736F667420432F432B2B204D534620372E30300D0A1A44530..... بقیهی کدها حذف شده AS N'prgJalaliDate.pdb' GO /****** Object: UserDefinedType [dbo].[JalaliDate] Script Date: 2013/04/30 08:27:00 ب.ظ ******/ CREATE TYPE [dbo].[JalaliDate] EXTERNAL NAME [prgJalaliDate].[JalaliDate] GO
دنباله دارد ...
یک نکتهی تکمیلی: ابزار CLI مایکروسافت برای تولید خودکار کدهای کلاینت #C براساس استاندارد OpenAPI
ابزار جدید Microsoft.dotnet-openapi کار تولید کدهای یک کلاینت سیشارپی را بر اساس OpenAPI Specification انجام میدهد. برای استفادهی از آن، ابتدا با استفاده از دستور dotnet tool install -g Microsoft.dotnet-openapi، کار نصب ابزار CLI آن انجام خواهد شد و سپس برای استفاده از آن، به فایل csproj. خود، تنظیمات زیر را اضافه کنید:
<Project Sdk="Microsoft.NET.Sdk.Worker"> <ItemGroup> <OpenApiReference Include="OpenAPIs\v1.json" CodeGenerator="NSwagCSharp" Namespace="GettingStarted" ClassName="MyClient"> <SourceUri>https://geo.api.vlaanderen.be/capakey/v2/swagger/docs/v1</SourceUri> </OpenApiReference> </ItemGroup> </Project>
حتی بر اساس این ابزار CLI، خود ویژوال استودیو هم گزینهی جدید Add –> Service Reference -> OpenAPI را اضافه کردهاست که تنظیمات یاد شدهی فوق را توسط یک رابط کاربری جدید دریافت میکند:
یک نکته تکمیلی
جهت لود کردن descriptionهای هر action و controller در لایبرری swagger بدین شکل میتوان عمل کرد.
services.AddSwaggerGen(c => { c.SwaggerDoc("v1", new Info { Version = "v1", Title = "My API", Description = "My Web API", TermsOfService = "None", Contact = new Contact { Name = "Mohammad Ahmadi", Email = string.Empty, Url = "https://www.dntips.ir" }, License = new License { Name = "Use under LICX", Url = "https://example.com/license" } }); var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml"; var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile); c.IncludeXmlComments(xmlPath); });
<PropertyGroup> <GenerateDocumentationFile>true</GenerateDocumentationFile> </PropertyGroup>
<PropertyGroup> <GenerateDocumentationFile>true</GenerateDocumentationFile> <NoWarn>$(NoWarn);1591</NoWarn> </PropertyGroup>
مطالب دورهها
بایدها و نبایدهای استفاده از IoC Containers
طوری با IoC Containers کار کنید که انگار وجود خارجی ندارند
تفاوت پایهای که بین یک فریم ورک IoC و سایر فریم ورکها وجود دارد، در معکوس شدن مسئولیتها است. در اینجا لایههای مختلف برنامه شما نیستند که فریم ورک IoC را فراخوانی میکنند؛ بلکه این فریم ورک IoC است که از جزئیات ارتباطات و وابستگیهای سیستم شما آگاه است و نهایتا کار کنترل وهله سازی اشیاء مختلف را عهده دار خواهد شد. طول عمر آنها را تنظیم کرده یا حتی در بعضی از موارد مانند برنامه نویسی جنبهگرا یا AOP، نسبت به تزئین این اشیاء یا دخالت در مراحل مختلف فراخوانی متدهای آنها نیز نقش خواهد داشت. نکتهی مهم در اینجا، نا آگاهی برنامه از حضور آنها است.
بنابراین در پروژه شما اگر ماژولها و لایههای مختلفی حضور دارند، تنها برنامه اصلی است که باید ارجاعی را به فریم ورک IoC داشته باشد و نه سایر لایههای سیستم. علت حضور آن در ریشه سیستم نیز تنها باید به اصطلاحا bootstrapping و اعمال تنظیمات مرتبط با آن خلاصه شود.
به عبارتی استفاده صحیح از یک فریم ورک IoC نباید به شکل الگوی Service Locator باشد؛ حالتی که در تمام قسمتهای برنامه مدام مشاهده میکنید resolver.Resolve، resolver.Resolve و الی آخر. باید از این نوع استفاده از فریم ورکهای IoC تا حد ممکن حذر شود و کدهای برنامه نباید وابستگی مستقیم ثانویهای را به نام خود فریم ورک IoC پیدا کنند.
نمونهای از نحوه صحیح استفاده از یک IoC Container را مشاهده میکنید. تنها در سه نقطه است که یک IoC container باید حضور پیدا کند:
الف) در آغاز برنامه برای اعمال تنظیمات اولیه و bootstrapping
ب) پیش از اجرای عملی جهت وهله سازی وابستگیهای مورد نیاز
ج) پس از اجرای عمل مورد نظر جهت آزاد سازی منابع
نکته مهم اینجا است که در حین اجرای فرآیند، این فرآیند باید تا حد ممکن از حضور IoC container بیخبر باشد و کار تشکیل اشیاء باید خارج از منطق تجاری برنامه انجام شود: IoC container خود را صدا نزنید؛ او شما را صدا خواهد زد.
عنوان شد تا «حد ممکن». این تا حد ممکن به چه معنایی است؟ اگر کار وهله سازی اشیاء را میتوانید تحت کنترل قرار دهید، مثلا آیا میتوانید در نحوه وهله سازی کنترلرها در ASP.NET MVC دخل و تصرف کرده و در زمان وهله سازی، اینکار را به یک IoC Container واگذار کنید؟ اگر بلی، دیگر به هیچ عنوانی نباید داخل کلاسهای فراخوانی شده و تزریق شده به کنترلرهای برنامه اثری از IoC Container شما مشاهده شود. زیرا این فریم ورکها اینقدر توانمند هستند که بتوانند تا چندین لایه از سیستم را واکاوی کرده و وابستگیهای لازم را وهله سازی کنند.
اگر خیر (نمیتوانید کار وهله سازی اشیاء را مستقیما تحت کنترل قرار دهید)؛ مانند تهیه یک Role Provider سفارشی در ASP.NET MVC که کار وهله سازی این Role Provider راسا توسط موتور ASP.NET انجام میشود و در این بین امکان دخل و تصرفی هم در آن ممکن نیست، آنگاه مجاز است داخل این کلاس ویژه از متدهای container.Resolve استفاده کرد؛ چون چارهی دیگری وجود ندارد و IoC Container نیست که کار وهله سازی ابتدایی آنرا عهده دار شده است. باید دقت داشت به این حالت خاص دیگر تزریق وابستگیها گفته نمیشود؛ بلکه نام الگوی آن Service locator است. در Service locator یک کامپوننت خودش به دنبال وابستگیهای مورد نیازش میگردد. در حالت تزریق وابستگیها، یک کامپوننت وابستگیهای مورد نیاز را درخواست میکند.
یک مثال:
کاری که در اینجا انجام شده است نمونه اشتباهی از استفاده از یک IoC Container میباشد. به صرف اینکه مشغول به استفاده از یک IoC Container هستیم به این معنا نیست که واقعا الگوی معکوس سازی وابستگیها را درست درک کردهایم. در اینجا الگوی Service locator مورد استفاده است و نه الگوی تزریق وابستگیها. به عبارتی در مثال فوق، کلاس ExampleClass وابسته است به یک وابستگی جدیدی به نام Container، علاوه بر وابستگی IService ایی که به او قرار است خدماتی را ارائه دهد.
نمونه اصلاح شده کلاس فوق، تزریق وابستگیها در سازنده کلاس به نحو زیر است:
در اینجا این کلاس است که وابستگیهای خود را درخواست میکند و نه اینکه خودش به دنبال آنها بگردد.
نمونه دیگری از کلاسی که خودش به دنبال یافتن و وهله سازی وابستگیهای مورد نیازش است مثال زیر میباشد:
به این کار poor man's dependency injection هم گفته میشود؛ اولین سازنده از طریق یک default constructor سعی کرده است وابستگیهای کلاس را، خودش تامین کند. باز هم کلاس میداند که به چه وابستگی خاصی نیاز دارد و عملا معکوس سازی وابستگیها رخ نداده است. همچنین استفاده از این حالت زمانیکه کلاس Dinner خودش وابستگی به کلاسهای دیگر داشته باشد، بسیار به هم ریخته و مشکل خواهد بود. مزیت استفاده از IoC Containers وهله سازی یک large object graph کامل است. به علاوه توسط IoC Containers مدیریت طول عمر اشیاء را نیز میتوان تحت نظر قرار داد. برای مثال میتوان به یک IoC Container گفت تنها یک وهله از DbContext را در طول یک درخواست ایجاد و آنرا در اختیار لایههای مختلف برنامه قرار بده؛ چون نیاز داریم کاری که در طی یک درخواست انجام میشود، در داخل یک تراکنش انجام شده و همچنین بیجهت به ازای هر new DbConetxt جدید، یکبار اتصالی به بانک اطلاعاتی باز و بسته نشود (سرعت بیشتر، سربار کمتر).
تفاوت پایهای که بین یک فریم ورک IoC و سایر فریم ورکها وجود دارد، در معکوس شدن مسئولیتها است. در اینجا لایههای مختلف برنامه شما نیستند که فریم ورک IoC را فراخوانی میکنند؛ بلکه این فریم ورک IoC است که از جزئیات ارتباطات و وابستگیهای سیستم شما آگاه است و نهایتا کار کنترل وهله سازی اشیاء مختلف را عهده دار خواهد شد. طول عمر آنها را تنظیم کرده یا حتی در بعضی از موارد مانند برنامه نویسی جنبهگرا یا AOP، نسبت به تزئین این اشیاء یا دخالت در مراحل مختلف فراخوانی متدهای آنها نیز نقش خواهد داشت. نکتهی مهم در اینجا، نا آگاهی برنامه از حضور آنها است.
بنابراین در پروژه شما اگر ماژولها و لایههای مختلفی حضور دارند، تنها برنامه اصلی است که باید ارجاعی را به فریم ورک IoC داشته باشد و نه سایر لایههای سیستم. علت حضور آن در ریشه سیستم نیز تنها باید به اصطلاحا bootstrapping و اعمال تنظیمات مرتبط با آن خلاصه شود.
به عبارتی استفاده صحیح از یک فریم ورک IoC نباید به شکل الگوی Service Locator باشد؛ حالتی که در تمام قسمتهای برنامه مدام مشاهده میکنید resolver.Resolve، resolver.Resolve و الی آخر. باید از این نوع استفاده از فریم ورکهای IoC تا حد ممکن حذر شود و کدهای برنامه نباید وابستگی مستقیم ثانویهای را به نام خود فریم ورک IoC پیدا کنند.
var container = BootstrapContainer(); var finder = container.Resolve<IDuplicateFinder>(); var processor = container.Resolve<IArgumentsParser>(); Execute( args, processor, finder ); container.Dispose();
الف) در آغاز برنامه برای اعمال تنظیمات اولیه و bootstrapping
ب) پیش از اجرای عملی جهت وهله سازی وابستگیهای مورد نیاز
ج) پس از اجرای عمل مورد نظر جهت آزاد سازی منابع
نکته مهم اینجا است که در حین اجرای فرآیند، این فرآیند باید تا حد ممکن از حضور IoC container بیخبر باشد و کار تشکیل اشیاء باید خارج از منطق تجاری برنامه انجام شود: IoC container خود را صدا نزنید؛ او شما را صدا خواهد زد.
عنوان شد تا «حد ممکن». این تا حد ممکن به چه معنایی است؟ اگر کار وهله سازی اشیاء را میتوانید تحت کنترل قرار دهید، مثلا آیا میتوانید در نحوه وهله سازی کنترلرها در ASP.NET MVC دخل و تصرف کرده و در زمان وهله سازی، اینکار را به یک IoC Container واگذار کنید؟ اگر بلی، دیگر به هیچ عنوانی نباید داخل کلاسهای فراخوانی شده و تزریق شده به کنترلرهای برنامه اثری از IoC Container شما مشاهده شود. زیرا این فریم ورکها اینقدر توانمند هستند که بتوانند تا چندین لایه از سیستم را واکاوی کرده و وابستگیهای لازم را وهله سازی کنند.
اگر خیر (نمیتوانید کار وهله سازی اشیاء را مستقیما تحت کنترل قرار دهید)؛ مانند تهیه یک Role Provider سفارشی در ASP.NET MVC که کار وهله سازی این Role Provider راسا توسط موتور ASP.NET انجام میشود و در این بین امکان دخل و تصرفی هم در آن ممکن نیست، آنگاه مجاز است داخل این کلاس ویژه از متدهای container.Resolve استفاده کرد؛ چون چارهی دیگری وجود ندارد و IoC Container نیست که کار وهله سازی ابتدایی آنرا عهده دار شده است. باید دقت داشت به این حالت خاص دیگر تزریق وابستگیها گفته نمیشود؛ بلکه نام الگوی آن Service locator است. در Service locator یک کامپوننت خودش به دنبال وابستگیهای مورد نیازش میگردد. در حالت تزریق وابستگیها، یک کامپوننت وابستگیهای مورد نیاز را درخواست میکند.
یک مثال:
public class ExampleClass { private readonly IService _service; public ExampleClass() { _service = Container.Resolve<IService>(); } public void DoSomething(int id) { _service.DoSomething(id); } }
نمونه اصلاح شده کلاس فوق، تزریق وابستگیها در سازنده کلاس به نحو زیر است:
public class ExampleClass { private IService _service; public ExampleClass(IService service) { _service = service; } public void DoSomething(int id) { _service.DoSomething(id); } }
نمونه دیگری از کلاسی که خودش به دنبال یافتن و وهله سازی وابستگیهای مورد نیازش است مثال زیر میباشد:
public class Search { IDinner _dinner; public Search(): this(new Dinner()) { } public Search(IDinner dinner) { _dinner = dinner; } }
چندسال قبل یک کنترل آپلود فایل در برنامههای ASP.NET Web forms در سایت Code projects منتشر شد که من در چند پروژه از آن استفاده کردم.
در ادامه نحوه سازگار سازی این مجموعه را با ASP.NET MVC مرور خواهیم کرد:
الف) سورسهای اصلی Flash کنترل ارسال فایلها
اگر علاقمند به تغییر اطلاعاتی در فایل فلش نهایی هستید به پوشه OriginalFlashSource پروژه پیوست شده مراجعه کنید. در اینجا برای مثال یک سری از برچسبهای آن فارسی شدهاند و کامپایل مجدد.
ب) مزیت استفاده از Flash uploader
با استفاده از Flash uploader امکان انتخاب چندین فایل با هم وجود دارد. همچنین در صفحه دیالوگ انتخاب فایلها دقیقا میتوان پسوند فایلهای مورد نظر را نیز تعیین کرد. این دو مورد در حالت ارسال معمولی فایلها به سرور و استفاده از امکانات معمولی HTML وجود ندارند. به علاوه امکان نمایش درصد پیشرفت آپلود فایلها و همچنین حذف کلی لیست و حذف یک آیتم از لیست را هم درنظر بگیرید.
ج) معادل کنترل Web forms را در ASP.NET MVC به شکل زیر میتوان تهیه کرد:
این اطلاعات در فایلی به نام FlashUploadHelper.cshtml در پوشه App_Code قرار خواهند گرفت.
د) نحوه استفاده از HTML helper فوق:
با کدهای کنترلری معادل:
توضیحات:
در اینجا uploadUrl، مسیر اکشن متدی است که قرار است اطلاعات فایلها را دریافت کند. queryParameters اختیاری است. اگر تعریف شود تعدادی کوئری استرینگ دلخواه را میتواند به متد Uploader ارسال کند. برای نمونه در اینجا User و Id ارسال شدهاند یا هر نوع کوئری استرینگ دیگری که مدنظر است.
flashUrl مسیر فایل SWF را مشخص میکند. در اینجا فایل FlashFileUpload.swfدر پوشه Content/FlashUpload قرار گرفته است.
fileTypeDescription برچسبی است که نوع فایلهای قابل انتخاب را به کاربر نمایش میدهد و fileTypes نوعهای مجاز قابل ارسال را دقیقا مشخص میکند.
پارامترهای uploadFileSizeLimit و totalUploadSizeLimit در صورتیکه مساوی صفر وارد شوند، به معنای عدم محدودیت اندازه در فایلها و جمع حجم ارسالی در هر بار است.
استفاده از پارامتر onUploadComplete اختیاری است. در اینجا میتوان پس از پایان عملیات از طریق جاوا اسکریپت عملیاتی را انجام داد. برای مثال اگر خواستید کاربر را به صفحه خاصی هدایت کنید، window.locationرا مقدار دهی نمائید.
در متد Uploader کنترلر فوق، پارامترهای User و id اختیاری بوده و بر اساس queryParameters متد FlashUploadHelper.AddFlashUploader مشخص میشوند. اما نام FileData نباید تغییری کند؛ از این لحاظ که دقیقا همین نام در فایل فلش، مورد استفاده قرار گرفته است.
در اکشن متد دریافت فایلها، لیستی از فایلهای ارسالی به سرور دریافت شده و سپس بر این اساس میتوان آنها را در مکانی مشخص ذخیره نمود.
دریافت پروژه
MvcFlashUploader.zip
در ادامه نحوه سازگار سازی این مجموعه را با ASP.NET MVC مرور خواهیم کرد:
الف) سورسهای اصلی Flash کنترل ارسال فایلها
اگر علاقمند به تغییر اطلاعاتی در فایل فلش نهایی هستید به پوشه OriginalFlashSource پروژه پیوست شده مراجعه کنید. در اینجا برای مثال یک سری از برچسبهای آن فارسی شدهاند و کامپایل مجدد.
ب) مزیت استفاده از Flash uploader
با استفاده از Flash uploader امکان انتخاب چندین فایل با هم وجود دارد. همچنین در صفحه دیالوگ انتخاب فایلها دقیقا میتوان پسوند فایلهای مورد نظر را نیز تعیین کرد. این دو مورد در حالت ارسال معمولی فایلها به سرور و استفاده از امکانات معمولی HTML وجود ندارند. به علاوه امکان نمایش درصد پیشرفت آپلود فایلها و همچنین حذف کلی لیست و حذف یک آیتم از لیست را هم درنظر بگیرید.
ج) معادل کنترل Web forms را در ASP.NET MVC به شکل زیر میتوان تهیه کرد:
@helper AddFlashUploader( string uploadUrl, string queryParameters, string flashUrl, int totalUploadSizeLimit = 0, int uploadFileSizeLimit = 0, string fileTypes = "", string fileTypeDescription = "", string onUploadComplete = "") { onUploadComplete = string.IsNullOrEmpty(onUploadComplete) ? "" : "completeFunction=" + onUploadComplete; queryParameters = Server.UrlEncode(queryParameters); fileTypes = string.IsNullOrEmpty(fileTypes) ? "" : "&fileTypes=" + Server.UrlEncode(fileTypes); fileTypeDescription = string.IsNullOrEmpty(fileTypeDescription) ? "" : "&fileTypeDescription=" + Server.UrlEncode(fileTypeDescription); var totalUploadSizeLimitData = totalUploadSizeLimit > 0 ? "&totalUploadSize=" + totalUploadSizeLimit : ""; var uploadFileSizeLimitData = uploadFileSizeLimit > 0 ? "&fileSizeLimit=" + uploadFileSizeLimit : ""; var flashVars = onUploadComplete + fileTypes + fileTypeDescription + totalUploadSizeLimitData + uploadFileSizeLimitData + "&uploadPage=" + uploadUrl + "?" + queryParameters; <object classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000" codebase="http://fpdownload.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=9,0,0,0" width="575" height="375" id="fileUpload" align="middle"> <param name="allowScriptAccess" value="sameDomain" /> <param name="movie" value="@flashUrl" /> <param name="quality" value="high" /> <param name="wmode" value="transparent"> <param name=FlashVars value="@flashVars"> <embed src="@flashUrl" FlashVars="@flashVars" quality="high" wmode="transparent" width="575" height="375" name="fileUpload" align="middle" allowScriptAccess="sameDomain" type="application/x-shockwave-flash" pluginspage="http://www.macromedia.com/go/getflashplayer" /> </object> }
د) نحوه استفاده از HTML helper فوق:
@{ ViewBag.Title = "Index"; var uploadUrl = Url.Action("Uploader", "Home"); var flashUrl = Url.Content("~/Content/FlashUpload/FlashFileUpload.swf"); } <h2> Flash Uploader</h2> <div style="background: #E0EBEF;"> @FlashUploadHelper.AddFlashUploader( uploadUrl: uploadUrl, queryParameters: "User=Vahid&Id=تست", flashUrl: flashUrl, fileTypeDescription: "Images", fileTypes: "*.gif; *.png; *.jpg; *.jpeg", uploadFileSizeLimit: 0, totalUploadSizeLimit: 0, onUploadComplete: "alert('انجام شد');") </div>
using System.Collections.Generic; using System.IO; using System.Web; using System.Web.Mvc; namespace MvcFlashUpload.Controllers { public class HomeController : Controller { public ActionResult Index() { return View(); } public ActionResult Uploader(string User, string Id, IEnumerable<HttpPostedFileBase> FileData) { var queryParameter1 = User; var queryParameter2 = Id; // ... foreach (var file in FileData) { if (file.ContentLength > 0) { var fileName = Path.GetFileName(file.FileName); var path = Path.Combine(Server.MapPath("~/App_Data/Uploads"), fileName); file.SaveAs(path); } } return Content(" "); } } }
توضیحات:
در اینجا uploadUrl، مسیر اکشن متدی است که قرار است اطلاعات فایلها را دریافت کند. queryParameters اختیاری است. اگر تعریف شود تعدادی کوئری استرینگ دلخواه را میتواند به متد Uploader ارسال کند. برای نمونه در اینجا User و Id ارسال شدهاند یا هر نوع کوئری استرینگ دیگری که مدنظر است.
flashUrl مسیر فایل SWF را مشخص میکند. در اینجا فایل FlashFileUpload.swfدر پوشه Content/FlashUpload قرار گرفته است.
fileTypeDescription برچسبی است که نوع فایلهای قابل انتخاب را به کاربر نمایش میدهد و fileTypes نوعهای مجاز قابل ارسال را دقیقا مشخص میکند.
پارامترهای uploadFileSizeLimit و totalUploadSizeLimit در صورتیکه مساوی صفر وارد شوند، به معنای عدم محدودیت اندازه در فایلها و جمع حجم ارسالی در هر بار است.
استفاده از پارامتر onUploadComplete اختیاری است. در اینجا میتوان پس از پایان عملیات از طریق جاوا اسکریپت عملیاتی را انجام داد. برای مثال اگر خواستید کاربر را به صفحه خاصی هدایت کنید، window.locationرا مقدار دهی نمائید.
در متد Uploader کنترلر فوق، پارامترهای User و id اختیاری بوده و بر اساس queryParameters متد FlashUploadHelper.AddFlashUploader مشخص میشوند. اما نام FileData نباید تغییری کند؛ از این لحاظ که دقیقا همین نام در فایل فلش، مورد استفاده قرار گرفته است.
در اکشن متد دریافت فایلها، لیستی از فایلهای ارسالی به سرور دریافت شده و سپس بر این اساس میتوان آنها را در مکانی مشخص ذخیره نمود.
دریافت پروژه
MvcFlashUploader.zip
ببین دوست من مطلبتون رو خوندم هم اینو و هم قبلی رو، ازش خوشم اومد اما چیزی راجب درج صریح یا بروز رسانی مقادیر Identity ننوشته بودین. یا اینکه نمیشه در یک جدول دو identity property داشت.
من بلدم با set identity_insert table_name on/off کاری کنم که خودم دستی مقداری را برای خصیصه identity لحاظ کنم. ولی متاسفانه نتونستم مقدار یک ستون با خصیصه Identity رو بروز رسانی (یا همون update) کنم. لطفا بهم بگید که اصلا این کار ممکنه یا من بلد نیستم. البته براساس query زیر بمن SQL Server گفته که نمیشه این ستون را update کرد که ظاهرا هم همین طور(ستون id همانطور که در پیام آمده از نوع identity هست)
اصلا اجازه بدین یه جور دیگه سوال رو مطرح کنم من نیاز دارم تمام مقادیر identity رو بروز رسانی کنم تا کاملا پشت سر هم و متوالی بشن این کار را میتونم با یک تابع row_number و یک derived table انجام بدم (اگر بذارن!) همانطور که قبلا نشان دادم، یا با روش زیر این کار را بکنم که البته اجرا نمیشه به این دلیل که در یک جدول نمیشه دو identity property داشت. با فرض اجرا شدن دستور select into باز هم در دستور update با مشکل بر میخوردیم (چون نمیشه ستون id را بروز رسانی کرد)
البته یک راهی برای حل این مساله هست اونم اینه که ابتدا بیاییم تمام دادهها جدول را در جدول دیگه ای درج کنیم سپس تمام دادههای جدول را حذف کنیم سپس دادههای حذف شده را با id جدید و مرتب شده در جدول اول درج کنیم. به این شکل
اما مشکلی که وجود داره اینه که اگر جدول ما parent باشه با مشکل واجه میشیم تمام سطرهای جداول child یتیم میشن.
من قصد ندارم صورت مساله نقد و بررسی بشه و اصولی بودن یا صحیح بودنش مورد ارزیابی قرار بگیره فقط برام این یک سوال شده.
مساله عمومی که راجب این ستون وجود داره استفاده کردن از Gapهای حاصل شده در این ستون برای درجهای بعدی است. که query آن نیز بسیار ساده و در دسترس است.
آیا شما میدانید که چگونه این مشکل با sequence ای که در نسخه 2012 معرفی شده است حل میشود؟
من بلدم با set identity_insert table_name on/off کاری کنم که خودم دستی مقداری را برای خصیصه identity لحاظ کنم. ولی متاسفانه نتونستم مقدار یک ستون با خصیصه Identity رو بروز رسانی (یا همون update) کنم. لطفا بهم بگید که اصلا این کار ممکنه یا من بلد نیستم. البته براساس query زیر بمن SQL Server گفته که نمیشه این ستون را update کرد که ظاهرا هم همین طور(ستون id همانطور که در پیام آمده از نوع identity هست)
update t set id = new_id from (select id, row_number() over(order by id) new_id from #temp)t --Cannot update identity column 'id'.
اصلا اجازه بدین یه جور دیگه سوال رو مطرح کنم من نیاز دارم تمام مقادیر identity رو بروز رسانی کنم تا کاملا پشت سر هم و متوالی بشن این کار را میتونم با یک تابع row_number و یک derived table انجام بدم (اگر بذارن!) همانطور که قبلا نشان دادم، یا با روش زیر این کار را بکنم که البته اجرا نمیشه به این دلیل که در یک جدول نمیشه دو identity property داشت. با فرض اجرا شدن دستور select into باز هم در دستور update با مشکل بر میخوردیم (چون نمیشه ستون id را بروز رسانی کرد)
select id, identity(int, 1,1) new_id into #temptable from #temp order by id asc /* cannot add identity column, using the SELECT INTO statement, to table '#temptable', which already has column 'id' that inherits the identity property. */ update t set id = new_id from #temp t join #temptable d on t.id = d.id;
declare @t table(id int) insert into @t select id from #temp delete from #temp set identity_insert #temp on insert #temp (id) select row_number() over(order by id) from @t set identity_insert #temp off
من قصد ندارم صورت مساله نقد و بررسی بشه و اصولی بودن یا صحیح بودنش مورد ارزیابی قرار بگیره فقط برام این یک سوال شده.
مساله عمومی که راجب این ستون وجود داره استفاده کردن از Gapهای حاصل شده در این ستون برای درجهای بعدی است. که query آن نیز بسیار ساده و در دسترس است.
آیا شما میدانید که چگونه این مشکل با sequence ای که در نسخه 2012 معرفی شده است حل میشود؟
گاهی از اوقات نیاز است در کوئریها از بین چندین مقدار یکی انتخاب و بجای مقدار اصلی، رشته یا عبارتی جایگزین، نوشته شود. پر استفادهترین راه حل پیشنهادی، استفاده از عبارت case در داخل کوئری هست که بر اساس موارد ممکن، عبارتهای برگشتی نوشته میشود. این راه حل خوبی به نظر میرسد اما اگر تعداد گزینهها زیاد شود باعث شلوغ شدن متن کوئری و اشکال در بازبینی و نگهداری آن خواهد شد.
یک راه حل دیگر استفاده از توابع نوع Scalar میباشد؛ به این صورت که میتوان مقدار استخراج شده از جدول را به تابع تعریف شده فرستاد و در ازاء، مقدار بازگشتی مناسبی را در خروجی مشاهده کرد. حال به یک مثال توجه کنید:
یک راه حل دیگر استفاده از توابع نوع Scalar میباشد؛ به این صورت که میتوان مقدار استخراج شده از جدول را به تابع تعریف شده فرستاد و در ازاء، مقدار بازگشتی مناسبی را در خروجی مشاهده کرد. حال به یک مثال توجه کنید:
Select Case Gen when 0 then 'مرد' when 1 then 'زن' end As Gen From Table
اکنون استفاده از تابع:
CREATE FUNCTION fcGenName ( @Gen tinyint ) RETURNS nvarchar(20) AS BEGIN -- Declare the return variable here DECLARE @gen nvarchar(20) -- Add the T-SQL statements to compute the return value here set @gen = (SELECT case @Gen when 0 then 'مرد' when 1 then 'زن' end as d) -- Return the result of the function RETURN @gen END
و فراخوانی تابع در متن کوئری :
Select fcGenName(Gen) From Table