EF Code First #4
- رشته اتصالی را در فایل کانفیگ پروژهای که مهاجرت روی آن فعال میشود نیز قرار دهید.
+ تمام این دستورات پارامتر رشته اتصالی هم دارند:
Update-Database -Verbose -ConnectionString "CONNECTIONSTRING" -ConnectionProviderName "System.Data.SqlClient" -StartupProjectName WEBSITE_PROJECT -ProjectName MIGRATION_PROJECT
یک. قبل از آپلود امکان برش وجود داشته باشد.
دو. سیستم برش قابلیت پشتیبانی از تناسب بین پهنا و ارتفاع را داشته باشد.
سه. قابلیت استفاده خارج از قواعد Ajax را داشته باشد و بتوان ارسال آن را به طور دستی کنترل کرد.
چهار. پشتیبانی برش تصاویر به صورت تاچ برای گوشیهای همراه را نیز دارا باشد.
کتابخانه jcrop کتابخانهای است که این امکانات را برای شما فراهم میکند. این کتابخانه بدین صورت است که در حین برش به شما 4 عدد x1,x2,y1,y2 را داده و شما با ارسال آن به سمت سرور میتوانید بر اساس این اعداد، عکس اصلی را برش بزنید. بدین صورت شما هم عکس اصلی را دارید و هم مختصات برش را دارید و اگر دوست دارید در جاهای مختلف از عکس اصلی برش داشته باشید، بسیار مفید خواهد بود.
مرحله اول:
ابتدا فایل jcrop را دانلود نمایید.
مرحله دوم:
کد Html زیر را به صفحه اضافه کنید:
<div> <!-- upload form --> <!-- hidden crop params --> <input type="hidden" id="x1" name="x1" /> <input type="hidden" id="y1" name="y1" /> <input type="hidden" id="x2" name="x2" /> <input type="hidden" id="y2" name="y2" /> <h2>ابتدا تصویر خود را انتخاب کنید</h2> <div><input type="file" name="postedFileBase" data-buttonText="انتخاب تصویر" id="image_file" onchange="fileSelectHandler()" /></div> <div></div> <div> <h2>قسمتی از تصویر را انتخاب نمایید</h2> <img id="preview" /> <div> <label>حجم فایل </label> <input type="text" id="filesize" name="filesize" /> <label>نوع فایل</label> <input type="text" id="filetype" name="filetype" /> <label>ابعاد فایل</label> <input style="direction: ltr;" type="text" id="filedim" name="filedim" /> </div> </div> </div>
تگ step2 نیز بعد از نمایش موفقیت آمیز تصویر نشان داده میشود که کاربر میتواند در آن تصویر را برش دهد و شامل بخش info نیز میباشد تا بتوان اندازه اصلی تصویر، نوع فایل تصویر Content Type و حجم آن را نمایش داد.
مرحله سوم:
سپس برای استایل دهی کدهای بالا از کد Css زیر استفاده میکنیم:
.bheader { background-color: #DDDDDD; border-radius: 10px 10px 0 0; padding: 10px 0; text-align: center; } .bbody { color: #000; overflow: hidden; padding-bottom: 20px; text-align: center; background: -moz-linear-gradient(#ffffff, #f2f2f2); background: -ms-linear-gradient(#ffffff, #f2f2f2); background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #ffffff), color-stop(100%, #f2f2f2)); background: -webkit-linear-gradient(#ffffff, #f2f2f2); background: -o-linear-gradient(#ffffff, #f2f2f2); filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffff', endColorstr='#f2f2f2'); -ms-filter: "progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffff', endColorstr='#f2f2f2')"; background: linear-gradient(#ffffff, #f2f2f2); } .bbody h2, .info, .error { margin: 10px 0; } .step2, .error { display: none; } .error { color: red; } .info { } label { margin: 0 5px; } .roundinput { border: 1px solid #CCCCCC; border-radius: 10px; padding: 4px 8px; text-align: center; width: 150px; } .jcrop-holder { display: inline-block; } input[type=submit] { background: #e3e3e3; border: 1px solid #bbb; border-radius: 3px; -webkit-box-shadow: inset 0 0 1px 1px #f6f6f6; box-shadow: inset 0 0 1px 1px #f6f6f6; color: #333; padding: 8px 0 9px; text-align: center; text-shadow: 0 1px 0 #fff; width: 150px; } input[type=submit]:hover { background: #d9d9d9; -webkit-box-shadow: inset 0 0 1px 1px #eaeaea; box-shadow: inset 0 0 1px 1px #eaeaea; color: #222; cursor: pointer; } input[type=submit]:active { background: #d0d0d0; -webkit-box-shadow: inset 0 0 1px 1px #e3e3e3; box-shadow: inset 0 0 1px 1px #e3e3e3; color: #000; }
مرحله چهارم:
افزودن کد جاوااسکریپتی زیر برای کار کردن با کتابخانه Jcrop میباشد:
function bytesToSize(bytes) { var sizes = ['بایت', 'کیلو بایت', 'مگابایت']; if (bytes == 0) return 'n/a'; var i = parseInt(Math.floor(Math.log(bytes) / Math.log(1024))); return (bytes / Math.pow(1024, i)).toFixed(1) + ' ' + sizes[i]; }; function updateInfo(e) { $('#x1').val(e.x); $('#y1').val(e.y); $('#x2').val(e.x2); $('#y2').val(e.y2); }; var jcrop_api, boundx, boundy; function fileSelectHandler() { var oFile = $('#image_file')[0].files[0]; $('.error').hide(); var rFilter = /^(image\/jpeg|image\/png)$/i; if (!rFilter.test(oFile.type)) { $('.error').html('فقط تصویر معتبر انتخاب نمایید').show(); return; } var oImage = document.getElementById('preview'); var oReader = new FileReader(); oReader.onload = function (e) { oImage.src = e.target.result; oImage.onload = function () { $('.step2').fadeIn(500); var sResultFileSize = bytesToSize(oFile.size); $('#filesize').val(sResultFileSize); $('#filetype').val(oFile.type); $('#filedim').val(oImage.naturalWidth + ' x ' + oImage.naturalHeight); if (typeof jcrop_api != 'undefined') { jcrop_api.destroy(); jcrop_api = null; $('#preview').width(oImage.naturalWidth); $('#preview').height(oImage.naturalHeight); } $('#preview').Jcrop({ aspectRatio: 2, bgFade: true, bgOpacity: .3, onChange: updateInfo, onSelect: updateInfo }, function () { //var bounds = this.getBounds(); //var boundx = bounds[0]; //var boundy = bounds[1]; // Store the Jcrop API in the jcrop_api variable jcrop_api = this; }); }; }; oReader.readAsDataURL(oFile); }
تابع fileSelectHandler
function fileSelectHandler() { var oFile = $('#image_file')[0].files[0]; $('.error').hide(); var rFilter = /^(image\/jpeg|image\/png)$/i; if (!rFilter.test(oFile.type)) { $('.error').html('فقط تصویر معتبر انتخاب نمایید').show(); return; }
در ادامه همین تابع بالا، کدهای زیر را اضافه میکنیم:
var oImage = document.getElementById('preview'); var oReader = new FileReader(); oReader.onload = function (e) { oImage.src = e.target.result; oImage.onload = function () { $('.step2').fadeIn(500); var sResultFileSize = bytesToSize(oFile.size); $('#filesize').val(sResultFileSize); $('#filetype').val(oFile.type); $('#filedim').val(oImage.naturalWidth + ' x ' + oImage.naturalHeight); if (typeof jcrop_api != 'undefined') { jcrop_api.destroy(); jcrop_api = null; $('#preview').width(oImage.naturalWidth); $('#preview').height(oImage.naturalHeight); } $('#preview').Jcrop({ aspectRatio: 2, bgFade: true, bgOpacity: .3, onChange: updateInfo, onSelect: updateInfo, onRelease: clearInfo }, function () { //var bounds = this.getBounds(); //var boundx = bounds[0]; //var boundy = bounds[1]; jcrop_api = this; }); }; }; oReader.readAsDataURL(oFile);
FileReader یکی از توابع موجود در HTML است که مستندات آن در سایت موزیلا موجود است و قابلیت خواندن غیرهمزمان فایلها و اشیا Blob را دارد. در خط آخر به عنوان پارامتر ما فایلی را که در آپلودر خوانده ایم و در مرحله قبل نوع فایل آن را بررسی کردیم، پاس میکنیم و باعث میشود که رویداد Load شیء FileReader صدا زده شود.
در این رویداد ابتدا اطلاعات این فایل را از قبیل سایز و ابعاد و نوع فایل، خوانده و در همان تگ Div که با کلاس info تعیین شده بود، نمایش میدهیم. سپس متغیر jcrop_api را که به صورت global در بالای تابع صدا زدیم، بررسی میکنم که آیا از قبل پر شدهاست یا خیر؟ اگر از قبل پرشدهاست باید شیء Jcrop را که به آن اعمال شده است، نابود و آن را نال کنیم تا برای تصویر جدید آماده شود. این کد زمانی کاربرد دارد که کاربر از تصویر قبلی انصراف دادهاست و تصویر جدیدی را انتخاب نموده است یا اینکه عملیات دارد به صورت ایجکسی پیاده میشود. اگر عملیات نابودی روی این پلاگین صورت نگیرد، برای مرتبه دوم کار نخواهد کرد.
سپس پلاگین جیکوئری Jcrop را بر روی آن اعمال میکنیم. در پرامتر اول یک سری تنظیمات اولیه را انجام میدهیم که در ادامه با آن آشنا میشویم و در پارامتر دوم یک callback را به آن پاس میکنیم تا بعد از آماده شدن پلاگین اجرا شود که در آن شیء جدید ایجاد شده یعنی this را در متغیری به اسم jcrop_api دخیره میکنیم تا در بررسیهای آتی که در بند بالا توضیح داده شد، در دسترس داشته باشیم. همچنین در این تابع شما میتوانید اندازه تصویر انتخابی را نیز داشته باشید.
این پلاگین شامل optionهای متفاوتی در پارامتر اول است که آنها را بررسی میکنیم:
MinSize : شما میتوانید حداقل پهنا و ارتفاعی را برای برش زدن تصویر در نظر بگیرید.
minSize:[40,20]
aspectRatio:1.5
bgOpacity از 0 تا یک مقدار میگیرد و میزان opacity محلهای تاریک را تعیین میکند. همچنین شامل سه رویداد onSelect,onChange,onrelease هم میباشد که به ترتیب در موارد زیر رخ میدهند:
ناحیه مورد نظر انتخاب شد.
ناحیه مورد نظر در حالت انتخاب است و ماوس در حال درگ شدن است و با هر حرکتی ماوس اجرا میگردد.
ناحیه انتخابی از حالت انتخاب خارج شد.
دو رویداد اول یعنی onchange و onSelect را برای به روزسانی فیلدهای مخفی و مختصات استفاده میکنیم:
function updateInfo(e) { $('#x1').val(e.x); $('#y1').val(e.y); $('#x2').val(e.x2); $('#y2').val(e.y2); };
این مختصات از طریق یک پارامتر به آنها پاس میشود. به غیر از این چهار عدد مختصات میتوانید با استفاده از متغیرهای w و h هم اندازه پهنا و ارتفاع محل برش خورده را نیز به دست آورید. هر چند که این اعداد، از تفریق خود مختصات هم به دست میآیند.
یک تابع جزئی دیگر هم در این فایل وجود دارد که حین نمایش اندازه تصویر، واحد نمایش مناسب آن را برای ما انتخاب میکند:
function bytesToSize(bytes) { var sizes = ['بایت', 'کیلو بایت', 'مگابایت']; if (bytes == 0) return 'n/a'; var i = parseInt(Math.floor(Math.log(bytes) / Math.log(1024))); return (bytes / Math.pow(1024, i)).toFixed(1) + ' ' + sizes[i]; };
بعد از اینکه کدهای سمت کلاینت را تمام کردیم لازم است با نحوه برش تصویر در سمت سرور هم آشنا شویم:
public static byte[] Resize(this byte[] byteImageIn, int x1,int y1,int x2,int y2) { ImageConverter ic = new ImageConverter(); Image src = (Image)(ic.ConvertFrom(byteImageIn)); Bitmap target = new Bitmap(x2 - x1, y2 - y1); using (Graphics graphics = Graphics.FromImage(target)) graphics.DrawImage(src, new Rectangle(0, 0, target.Width, target.Height), new Rectangle(x1,y1,x2-x1,y2-y1), GraphicsUnit.Pixel); src = target; using (var ms = new MemoryStream()) { src.Save(ms, System.Drawing.Imaging.ImageFormat.Jpeg); return ms.ToArray(); } }
از آنجا که ما تصاویر را در دیتابیس به صورت آرایهای از بایتها ذخیره میکنیم، extension method ذکر شده در بالا تصویر را در حالت آرایهای از بایتها برش میدهد. بدیهی که بسته به نیاز شما کد بالا دست خوش تغییراتی خواهد شد. ابتدا تصویر باینری را به شی Image تبدیل میکنیم و یک شیء Bitmap جدید را به عنوان بوم خالی و به اندازه کادر برش ایجاد میکنیم تا تصویر برش خورده در آن قرار بگیرد و سپس توسط متد DrawImage میخواهیم که تصویر مبدا را با مختصات شیء Rectangle از نقطه 0 و 0 بوم آغاز کرده و تا انتهای آن شروع به ترسیم کند. سپس آن را ذخیره و مجددا در قالب همان آرایهای از بایتها بر میگردانیم.
تنها یک نکته را به خاطر داشته باشید که مقادیر مختصاتی که پلاگین جی کوئری ارسال میکند در قالب اعداد اعشاری هستند و برای ارسال و دریافت آنها در سرور این نکته را به خاطر داشته باشید.
MVC Scaffolding #3
آشنایی با ساختار اصلی MVC Scaffolding
پس از نصب MVC Scaffolding از طریق NuGet به پوشه Packages مراجعه نمائید. در اینجا پوشههای MvcScaffolding، T4Scaffolding و T4Scaffolding.Core ساختار اصلی این بسته را تشکیل میدهند. برای نمونه اگر پوشه T4Scaffolding\tools را باز کنیم، شاهد تعدادی فایل ps1 خواهیم بود که همان فایلهای پاورشل هستند. مطابق طراحی NuGet، همواره فایلی با نام init.ps1 در ابتدا اجرا خواهد شد. همچنین در اینجا پوشههای T4Scaffolding\tools\EFRepository و T4Scaffolding\tools\EFDbContext نیز قرار دارند که حاوی قالبهای اولیه کدهای مرتبط با الگوی مخزن و DbContext تولیدی میباشند.
در پوشه MvcScaffolding\tools، ساختار قالبهای پیش فرض تولید Viewها و کنترلرهای تولیدی قرار دارند. در اینجا به ازای هر مورد، دو نگارش vb و cs قابل مشاهده است.
سفارشی سازی قالبهای پیش فرض Viewهای MVC Scaffolding
برای سفارشی سازی قالبهای پیش فرض از دستور کلی زیر استفاده میشود:
Scaffold CustomTemplate Name Template
Scaffold CustomTemplate View Index
اگر دستور فوق را اجرا کنیم، فایل جدیدی به نام CodeTemplates\Scaffolders\MvcScaffolding.RazorView\Index.cs.t4 به پروژه جاری اضافه میشود. از این پس کلیه فرامین اجرایی، از نسخه محلی فوق بجای نمونههای پیش فرض استفاده خواهند کرد.
در ادامه قصد داریم اندکی این قالب پیش فرض را جهت اعمال ویژگی DisplayName به هدر جدول تولیدی نمایش اطلاعات Tasks تغییر دهیم. در کلاس Task، خاصیت زمان موعود با ویژگی DisplayName مزین شده است. این نام نمایشی حین تولید فرمهای ثبت و ویرایش اطلاعات بکار گرفته میشود، اما در زمان تولید جدول اطلاعات ثبت شده، به هدر جدول اعمال نمیگردد.
[DisplayName("Due Date")] public DateTime? DueDate { set; get; }
// Describes the information about a property on the model class ModelProperty { public string Name { get; set; } public string DisplayName { get; set; } public string ValueExpression { get; set; } public EnvDTE.CodeTypeRef Type { get; set; } public bool IsPrimaryKey { get; set; } public bool IsForeignKey { get; set; } public bool IsReadOnly { get; set; } }
static string GetDisplayName(EnvDTE.CodeProperty prop) { var displayAttr = prop.Attributes.OfType<EnvDTE80.CodeAttribute2>().Where(x => x.FullName == typeof(System.ComponentModel.DisplayNameAttribute).FullName).FirstOrDefault(); if(displayAttr == null) { return prop.Name; } return displayAttr.Value.Replace("\"",""); }
اکنون برای اعمال متد GetDisplayName، متد GetEligibleProperties را یافته و به نحو زیر تغییر دهید:
results.Add(new ModelProperty { Name = prop.Name, DisplayName = GetDisplayName(prop), ValueExpression = "Model." + prop.Name, Type = prop.Type, IsPrimaryKey = Model.PrimaryKeyName == prop.Name, IsForeignKey = ParentRelations.Any(x => x.RelationProperty == prop), IsReadOnly = !prop.IsWriteable() });
اکنون قسمت هدر جدول تولیدی را در ابتدای فایل t4 یافته و به نحو زیر تغییر میدهیم تا از DisplayName استفاده کند:
<# List<ModelProperty> properties = GetModelProperties(Model.ViewDataType, true); foreach (ModelProperty property in properties) { if (!property.IsPrimaryKey && !property.IsForeignKey) { #> <th> <#= property.DisplayName #> </th> <# } } #>
PM> Scaffold Controller -ModelType Task -ControllerName TasksController -DbContextType TasksDbContext -Repository -Force
سفارشی سازی قالبهای پیش فرض کنترلرهای MVC Scaffolding
در ادامه قصد داریم کدهای الگوی مخزن تهیه شده را اندکی تغییر دهیم. برای مثال با توجه به اینکه از تزریق وابستگیها استفاده خواهیم کرد، نیازی به سازنده اولیه پیش فرض کنترلر که در بالای آن ذکر شده «در صورت استفاده از یک DI این مورد را حذف کنید»، نداریم. برای این منظور دستور زیر را اجرا کنید:
PM> Scaffold CustomTemplate Controller ControllerWithRepository
به این ترتیب فایل جدید CodeTemplates\Scaffolders\MvcScaffolding.Controller\ControllerWithRepository.cs.t4 به پروژه جاری اضافه خواهد شد. در این فایل چند سطر ذیل را یافته و سپس حذف کنید:
// If you are using Dependency Injection, you can delete the following constructor public <#= Model.ControllerName #>() : this(<#= String.Join(", ", Repositories.Values.Select(x => "new " + x.RepositoryTypeName + "()")) #>) { }
PM> Scaffold Controller -ModelType Task -ControllerName TasksController -DbContextType TasksDbContext -Repository -Force -ForceMode ControllerOnly
یا اگر نیاز به تغییر کدهای الگوی مخزن مورد استفاده است میتوان از دستور ذیل استفاده کرد:
Scaffold CustomScaffolder EFRepository
لیست Scaffolderهای مهیا با دستور Get-Scaffolder قابل مشاهده است.
در این مثال برای نمایش پیام به صورت notification، از کتابخانه toastr استفاده میکنیم که از طریق nuget میتوانید آن را به پروژه اضافه کنید:
PM> Install-Package toastr
toastr.info("نمایش یک پیام - info"); toastr.success("نمایش یک پیام - success"); toastr.error("نمایش یک پیام - error"); toastr.warning("نمایش یک پیام - warning");
toastr.success("نمایش یک پیام - success", "عنوان");
toastr.options = { tapToDismiss: true, toastClass: 'toast', containerId: 'toast-container', debug: false, showMethod: 'fadeIn', //fadeIn, slideDown, and show are built into jQuery showDuration: 300, showEasing: 'swing', //swing and linear are built into jQuery onShown: undefined, hideMethod: 'fadeOut', hideDuration: 1000, hideEasing: 'swing', onHidden: undefined, extendedTimeOut: 1000, iconClasses: { error: 'toast-error', info: 'toast-info', success: 'toast-success', warning: 'toast-warning' }, iconClass: 'toast-info', positionClass: 'toast-top-right', timeOut: 5000, // Set timeOut and extendedTimeOut to 0 to make it sticky titleClass: 'toast-title', messageClass: 'toast-message', target: 'body', closeHtml: '<button>×</button>', newestOnTop: true, preventDuplicates: false, progressBar: false };
public class NotificationHub : Hub { private readonly IProductService _productService; public NotificationHub(IProductService productService) { _productService = productService; } public void SendNotification() { Clients.Others.ShowNotification(_productService.GetLastProduct()); } }
var notify = $.connection.notificationHub; notify.client.showNotification = function (data) { toastr.info("رکورد جدیدی ثبت گردید جهت نمایش اینجا کلیک کنید"); }; $.connection.hub.start().done(function () { @{ if (ViewBag.NotifyUsers) { <text>notify.server.sendNotification();</text> } } });
var positionClasses = { topRight: 'toast-top-right', bottomRight: 'toast-bottom-right', bottomLeft: 'toast-bottom-left', topLeft: 'toast-top-left', topCenter: 'toast-top-center', bottomCenter: 'toast-bottom-center' }; var notify = $.connection.notificationHub; notify.client.showNotification = function (data) { toastr.options = { showDuration: 300, positionClass: positionClasses.bottomRight, onclick: function () { $('#table tr:last').after("<tr>" + "<td>" + data.Title + "</td>" + "<td>" + data.Description + "</td>" + "<td>" + data.Price + "</td>" + "<td>" + data.Category + "</td>" + "<td> </td>" + "</tr>"); } }; toastr.info("رکورد جدیدی ثبت گردید جهت نمایش اینجا کلیک کنید"); }; $.connection.hub.start().done(function () { @{ if (ViewBag.NotifyUsers) { <text>notify.server.sendNotification();</text> } } });
onclick: function () { $('#table tr:last').after("<tr>" + "<td>" + data.Title + "</td>" + "<td>" + data.Description + "</td>" + "<td>" + data.Price + "</td>" + "<td>" + data.Category + "</td>" + "<td> </td>" + "</tr>"); }
data {Id: 12, Title: "Item1", Description: "Des", Price: 100000, Category: 0}
import { SpecialCollection } from "../../special"; import { LoginComponent } from "../login"; import { TextUtils } from ".../../utils/text"; import { Router } from "../../../core/router";
import { Data } from '../data';
import { Data } from '../../../data';
خوشبختانه کامپایلر TypeScript به همراه تنظیمات baseUrl و paths است که توسط آنها میتوان این مسیرهای نسبی را به مسیرهای مطلق تبدیل کرد و در این حالت اهمیتی ندارد که ماژول مدنظر از چه سطحی و درون چه پوشهی تو در تویی فراخوانی میشود، این مسیر import همواره ثابت خواهد بود.
تنظیمات فایل tsconfig.json برای معرفی مسیرهای مطلق ماژولها
فرض کنید میخواهید از یکی از سرویسهای Core Module استفاده کنید:
بسته به عمق پوشهی استفاده کننده، به یک چنین تعریفی خواهید رسید:
import { BrowserStorageService } from "./../../core/browser-storage.service";
{ "compilerOptions": { "baseUrl": "src", "paths": { "@app/*": [ "app/*" ], "@app/core/*": [ "app/core/*" ], "@app/shared/*": [ "app/shared/*" ], "@env/*": [ "environments/*" ] } } }
پس از این تغییرات، اکنون افزونهی پیشنهاد دهندهی imports، هر دو حالت استفادهی از مسیر مطلق بر اساس نام مستعار تعریف شده:
import { BrowserStorageService } from "@app/core/browser-storage.service";
import { BrowserStorageService } from "./../../core/browser-storage.service";
برای مثال اگر دقت کرده باشید، روش import اجزای خود Angular به صورت زیر است:
import { Component } from '@angular/core';
"paths": { "@angular/*": ["node_modules/@angular/*"] },
یک نکتهی مهم: تنظیمات فوق بدون تنظیمات معادل webpack ناقص هستند
اگر از برنامهی Angular CLI استفاده میکنید، تنظیمات ذکر شده، تا همینجا به پایان میرسند؛ چون webpack جزئی از Angular CLI است و تنظیمات پیش فرض آن، قسمت baseUrl و paths فایل tsconfig.json را به صورت خودکار پردازش میکند. اما اگر از TypeScript در محیطهای دیگری استفاده میکنید که از webpack به صورت مجزایی استفاده میکنند، نیاز است قسمت resolve.alias فایل webpack.config.js را نیز جهت معرفی این تغییرات، اصلاح کنید. از این جهت که کامپایلر TypeScript این مسیرهای مطلق را در حین تولید فایلهای نهایی JavaScript ایی معادل، به مسیرهای نسبی بازنویسی نمیکند و در این حالت webpack نمیداند که چطور باید این ماژولها را یافته و یکی کند.
resolve: { extensions: ['*', '.js', '.ts'], modules: [ rootDir, path.join(rootDir, 'node_modules') ], alias: { '@app': 'src/app' } },
کوتاه کردن مسیرهای مطلق با معرفی فایل ویژهی index.ts
تا اینجا بجای ذکر مسیر
import { BrowserStorageService } from "./../../core/browser-storage.service";
import { BrowserStorageService } from "@app/core/browser-storage.service";
import { BrowserStorageService } from "@app/core";
برای اینکار نیاز است فایل ویژهای را به نام index.ts، در ریشهی پوشهی core ایجاد کنیم (src\app\core\index.ts)، با این محتوا:
export * from "./browser-storage.service"; export * from "./app-config.service"; export * from "./seo-service";
برای نمونه اگر به پوشهی node_modules\@angular خود مجموعهی Angular هم مراجعه کنید، هر پوشهی src آن به همراه یک فایل index.d.ts شبیه تعاریف فوق نیز هست.
پس از افزودن فایل index.ts به ریشهی پوشهی مدنظر، اکنون در لیست پیشنهادات، ذکر app/core@ تنها نیز ظاهر شده و استفادهی از آن مجاز است:
چگونه میشود این قاعده را از بین برد و دقیقا همانند مدلها این نامگذاری انجام شود؟
Select { [Measures].[Internet Sales Amount], [Measures].[Internet Tax Amount] } on columns, head( [Customer].[Customer Geography].[Country], 2 )on rows From [Adventure Works]
تابع Head، تعداد مشخص شده بر اساس پارامتر اول از آن محور را بر اساس نحوهی نمایش تنظیم شده در SSAS، واکشی میکند.
حال تصور کنید بخواهیم شرط زیر را بر روی کوئری بالا اجرا کنیم
( [Measures].[Internet Sales Amount] >= '2500000' )
به عبارت دیگر ما میخواهیم دو کشوری را انتخاب کنیم که میزان فروش اینترنتی آنها بالای 2500000 باشد.
کوئری مشابه زیر میباشد
Select { [Measures].[Internet Sales Amount], [Measures].[Internet Tax Amount] } on columns, head( [Customer].[Customer Geography].[Country], 2 )on rows From [Adventure Works] Where ( [Measures].[Internet Sales Amount] >= '2500000' )
البته خطای زیر را خواهیم داشت.
به یاد داشته باشیم در صورتیکه بخواهیم ایجاد محدودیت در نمایش دادهها را در یک محور داشته باشیم، باید از تابع Filter استفاده کنیم؛ به صورت زیر:
Select Filter( { [Measures].[Internet Sales Amount], [Measures].[Internet Tax Amount] } , [Measures].[Internet Sales Amount] >= 2644017.71 ) on columns, head( [Customer].[Customer Geography].[Country], 3 )on rows From [Adventure Works]
تابع Filter دو پارامتر می گیرد. پارامتر اول نام ردیف یا ستونی می باشد که روی آن می خواهیم عمل فیلتر را انجام دهیم. پارامتر دوم شرط فیلترینگ می باشد که می بایست مانند T/SQL دارای یک خروجی Boolean باشد
همچنان نتیجه درست نمیباشد ! چرا؟
اگر بخواهیم شرط روی Axis ردیف (کشور ها) اعمال گردد، باید عملیات فیلترینگ در این Axis انجام شود . بنابر این خروجی بدست آمده صحیح نمی باشد زیرا ما عملیات فیلترینگ را روی ستون ها انجام داده ایم.
کوئری زیر را اجرا نمایید
Select { [Measures].[Internet Sales Amount] ,[Measures].[Internet Tax Amount] } on columns, head( Filter( [Customer].[Customer Geography].[Country] , [Measures].[Internet Sales Amount] >= 2644017.71 ), 3) on rows From [Adventure Works]
البته توجه کنید که این کوئری، سه کشور اول که در شرط زیر قرار دارند را بر می گرداند و الزاما این سه کشور از تمام کشور های دیگر بیشتر نمی باشند.
در این حالت سه کشور که بالاتر از مقدار ذکر شده، فروش اینترنتی دارند، در خروجی قرار می گیرند . البته این سه کشور دارای بالاترین فروش نمی باشند بلکه به ترتیب اسم، از بالا گزینش انجام شده است و بعد از پیدا کردن سه کشور که در شرط قرار بگیرند، جستجو تمام شده است .
اگر بخواهیم سه کشوری را که بالاترین میزان فروش را دارند پیدا کنیم و شرط هم همواره اعمال گردد، کوئری زیر درست می باشد:
Select { [Measures].[Internet Sales Amount] ,[Measures].[Internet Tax Amount] } on columns, TopCount( Filter( [Customer].[Customer Geography].[Country] , [Measures].[Internet Sales Amount] >= 2644017.71 ), 3, [Measures].[Internet Sales Amount]) on rows From [Adventure Works]
در این حالت به جای تابع Head از تابع TopCount استفاده گردیده است .این تابع سه کشوری را که بیشترین فروش اینترنتی را داشته اند و این فروش بالاتر از مقدار ذکر شده در شرط می باشد را بر می گرداند .البته در اینجا تابع topcount دارای سه پارامتر می باشد و در پارامتر سوم اعلام میکند که تعداد بالای مجموعه براساس چه شاخصی باید به دست بیاید.
حال اگر بخواهیم سه ردیف انتهایی جدول را واکشی کنیم داریم:
Select { [Measures].[Internet Sales Amount], [Measures].[Internet Tax Amount] }on columns, tail([Customer].[Customer Geography].[Country], 3)on rows From [Adventure Works]
این تابع برعکس تابع Head کار میکند و N ردیف آ اخر مجموعه را بدست می آورد . البته در بالا فقط 3 ردیف انتهایی را
در خروجی آورده ایم و هیچ شرطی اعمال نگردیده است.
نصب وب سرور آپاچی
به اینترنت متصل شده، ترمینال Ubuntu را گشوده (با میانبر ctrl+alt+t) و سپس فرمانهای ذیل را صادر کنید:
sudo apt-get update sudo apt-get upgrade -y sudo apt-get install apache2
نصب ماژولهای ASP.NET مخصوص آپاچی
سپس نیاز است ASP.NET runtime for Apache را نصب کنیم:
sudo apt-get install libapache2-mod-mono mono-apache-server4
اگر علاقمند به مشاهده تنظیمات آن بودید باید به مسیر etc/apache2/mods-enabled مراجعه کرده و فایل mod_mono.conf را بررسی کنید (اختیاری). برای مثال، در آن حالت اجرا، بر روی ASP.NET 4 تنظیم شدهاست.
تنظیمات آپاچی برای کار با ASP.NET
نیاز است فایل تنظیمات پیش فرض وب سرور آپاچی را جهت معرفی ASP.NET به آن، اندکی ویرایش کنیم:
sudo gedit /etc/apache2/sites-available/default
MonoAutoApplication disabled AddHandler mono .aspx .ascx .asax .ashx .config .cs .asmx .axd MonoApplications "/:/var/www" MonoServerPath default "/usr/bin/mod-mono-server4"
SetHandler mono DirectoryIndex index.aspx index.html default.aspx Default.aspx
<DirectoryMatch "/([bB]in|[Aa]pp_[Cc]ode|[Aa]pp_[Dd]ata|[Aa]pp_[Gg]lobal[Rr]esources|[Aa]pp_[Ll]ocal[Rr]esources)/"> Order deny,allow Deny from all </DirectoryMatch>
sudo service apache2 restart
# r,w,exec for user + group sudo chgrp -R www-data /var/www sudo chown -R www-data /var/www sudo chown -R www-data:www-data /var/www sudo chmod -R 755 /var/www
gedit /var/www/index.html
اجرای مثالها
با تنظیمات فوق، برنامههای کپی شده در مسیر var/www به کمک مونو و آپاچی اجرا خواهند شد.
دسترسیهای فعلی کاربر وارد شده به لینوکس اجازه کپی فایلها را به مسیر var/www نمیدهد. همچنین میخواهیم این کارها را توسط File browser آن انجام دهیم و نه خط فرمان. برای این منظور دستور ذیل را اجرا کنید تا File browser آن با دسترسی مدیریتی اجرا شود:
sudo nautilus
پس از کپی دو برنامه Web forms و MVC ابتدای بحث، نیاز است مجددا فایل تنظیمات آپاچی را ویرایش کنیم:
sudo gedit /etc/apache2/sites-available/default
AddMonoApplications default "/webforms_test:/var/www/WebFormsApp/" <Location /webforms_test> SetHandler mono </Location>
و تنظیم برنامه MVC به صورت زیر میباشد:
AddMonoApplications default "/mvc_test:/var/www/Mvc4ProjectApp/" <Location /mvc_test> SetHandler mono </Location>
اکنون برنامه وب فرمها در مسیر http://127.0.0.1/webforms_test و برنامه MVC در مسیر http://127.0.0.1/mvc_test قابل دسترسی است.
خلاصه بحث
پس از نصب وب سرور آپاچی و ماژول مونوی مخصوص آن، فایل etc/apache2/sites-available/default را به نحو ذیل ویرایش کنید و مسیرهای برنامههای خود را در آن تعریف نمائید:
<VirtualHost *:80> ServerAdmin webmaster@localhost MonoAutoApplication disabled AddHandler mono .aspx .ascx .asax .ashx .config .cs .asmx .axd MonoApplications "/:/var/www" MonoServerPath default "/usr/bin/mod-mono-server4" DocumentRoot "/var/www" <Directory /> Options FollowSymLinks AllowOverride None </Directory> <Directory /var/www/> SetHandler mono DirectoryIndex index.aspx index.html default.aspx Default.aspx Options Indexes Includes FollowSymLinks MultiViews AllowOverride None Order allow,deny allow from all </Directory> ScriptAlias /cgi-bin/ /usr/lib/cgi-bin/ <Directory "/usr/lib/cgi-bin"> AllowOverride None Options +ExecCGI -MultiViews +SymLinksIfOwnerMatch Order allow,deny Allow from all </Directory> <DirectoryMatch "/([bB]in|[Aa]pp_[Cc]ode|[Aa]pp_[Dd]ata|[Aa]pp_[Gg]lobal[Rr]esources|[Aa]pp_[Ll]ocal[Rr]esources)/"> Order deny,allow Deny from all </DirectoryMatch> AddMonoApplications default "/webforms_test:/var/www/WebFormsApp/" <Location /webforms_test> SetHandler mono </Location> AddMonoApplications default "/mvc_test:/var/www/Mvc4ProjectApp/" <Location /mvc_test> SetHandler mono </Location> ErrorLog ${APACHE_LOG_DIR}/error.log # Possible values include: debug, info, notice, warn, error, crit, # alert, emerg. LogLevel warn CustomLog ${APACHE_LOG_DIR}/access.log combined </VirtualHost>