هدف بررسی کامل مباحث Streaming در دات نت فریمورک میباشد.
Stream چیست؟
دنبالهای از بایتها که میتوان آنها را از یک backing store (انبار پشتیبان) خواند یا در آن نوشت.
Backing Store
یک رسانه ذخیره سازی از جمله Disk-Drive، Memory و Network Location میباشد که به عنوان منبع یا مقصدی برای خواندن و نوشتن بایتها به صورت دنبالهای، میتوان از آن استفاده کرد.
زمانی که قرار است داده ذخیره شده به صورت Stream مصرف شود، مزیت مقیاس پذیری را نیز خواهید داشت. لذا لازم نیست با مشکل محدودیت حافظه نیز درگیر شوید.
آشنایی با معماری Streaming در دات نت
Streaming در دات نت، توسط سه مفهوم: backing store، decorators و adapters در برگرفته شده است.
کلاسی به نام Stream در دات نت، برای ارائه یکسری متد مشترک برای Reading، Writing و Positioning در نظر گرفته شده است که همچنین کلاس پایه Backing Store Streams و Decorator Streams نیز میباشد.
اعضای کلاس Stream را میتوان به شکل زیر گروه بندی کرد:
در نظر داشته باشید که Stream ها، دارای اشاره گری به مکان جاری تحت عنوان Pointer نیز میباشند. مقدار پیش فرض آن «صفر» میباشد و زمانی که شروع به خواندن از Stream کنید، این خواندن از مکانی شروع میشود که Pointer به آنجا اشاره میکند. به شکل زیر توجه کنید:
اگر قرار باشد 3 بایت اول خوانده شود، لذا حالت زیر را خواهیم داشت:
همانطور که مشخص است، Pointer مربوط به Stream به اولین خانهای اشاره میکند که در Readهای بعدی قرار است خوانده شود. در نهایت با خواندن دو بایت دیگر، حالت زیر را خواهیم داشت:
برای Reading و Writing متدهای زیر در کلاس System.IO.Stream در نظر گرفته شدهاند:
(Read(byte[] buffer,int offset,int count
buffer: آرایهای از بایتها برای نگهداری دادهی خوانده شده از Stream
offset: برخلاف تصور، اندیسی است که مکان شروع ذخیره سازی در buffer را مشخص میکند و نه مکان شروع خواندن از Stream
count: بیشترین تعداد بایت برای خواندن از Stream میباشد. با توجه به اینکه ممکن است به انتهای Stream رسیده باشیم یا اینکه در شرایطی مثلا در Network Streamها چه بسا خود Stream تصمیم بگیرد تعداد بایت کمتری از این مقدار Count را برای ما ارائه دهد. از این رو همیشه مقداری که برای Count مشخص میکنید همان مقداری نیست که متد Read برای شما برگشت خواهد داد.
return: تعداد بایتهایی که خوانده شده است یا اگر به انتهای Stream رسیده باشیم «0» برگشت خواهد داد. از این رو تکه کد زیر برای خواندن کل داده به یکباره، قابل اطمینان نخواهد بود.
byte[] dataToRead=new byte[stream.Length]; int bytesRead=stream.Read(dataToRead,0,dataToRead.Length);
راه حل جایگزین میتواند به شکل زیر باشد:
static byte[] ReadBytes(Stream stream) { // dataToRead will hold the data read from the stream byte[] dataToRead = new byte[stream.Length]; //this is the total number of bytes read. this will be incremented //and eventually will equal the bytes size held by the stream int totalBytesRead = 0; //this is the number of bytes read in each iteration (i.e. chunk size) int chunkBytesRead = 1; while (totalBytesRead < dataToRead.Length && chunkBytesRead > 0) { chunkBytesRead = stream.Read(dataToRead, totalBytesRead, dataToRead.Length - totalBytesRead); totalBytesRead = totalBytesRead + chunkBytesRead; } return dataToRead; }
byte[] data = new BinaryReader (s).ReadBytes (1000);
return: یک بایت را از مکان فعلی که Pointer به آن اشاره میکند، میخواند. اگر خروجی «-1» باشد، به انتهای Stream رسیده اید.
برخلاف انتظار، خروجی این متد از نوع int میباشد؛ چرا که لازم است «-1» را نیز در برگیرد.
با توجه به حالت FileStream که فقط برای Append کردن وهله سازی شده است، امکان خواندن را نخواهید داشت. بنابراین زمانیکه از کلاس شخص ثالثی برای خواندن از Stream استفاده میکنید، بهصلاح است (به منظور Defensive Programming) که از متد CanRead قبل خواندن بهره ببرید.
(Write(byte[] array,int offset,int count
array: آرایه ای از بایتها که قرار است در Stream درج شوند.
offset: اندیس شروع array برای درج کردن در Stream را مشخص میکند.
count: بیشترین تعداد بایتی که از array در Stream درج خواهد شد.
برای درج یک بایت در Stream استفاده میشود.
برای تشخص پشتیبانی کردن Stream از عملیات درج کردن مورد استفاده قرار خواهد گرفت.
با انجام هر یک از عملیات Read و Write برروی Stream، باعث تغییر مکان Pointer مربوط به آن خواهید شد. در صورتیکه نیاز است به صورت انتخابی مکان خاصی از Stream را برای شروع درج کردن یا خواندن انتخاب کنید، Seeking کمک کننده خواهد بود.
باید توجه داشت که پشتیبانی از این عملیات به backing store مورد استفاده وابسته میباشد. از این رو باید دانست که MemoryStream و FileStream از Seeking پشتیبانی کرده ولی در مقابل NetworkStream، PipeStream و همچنین Decorator Streams به غیر از BufferedStream قابلیت Seeking را ندارند. BufferedStream با ایجاد پوششی برروی یک Stream به اصطلاح non-seekable، امکان Seeking درون Buffer داخلی خود را مهیا خواهد کرد.
برای عملیات Seeking نیز اعضایی در کلاس پایه System.IO.Stream در نظر گرفته شده است:
برای تنظیم مکان Pointer در Stream استفاده خواهد شد.
متدی برای تنظیم طول Stream، که اگر value ارسال شده کوچکتر از طول فعلی Stream باشد، آن را کوتاه کرده و در غیر این صورت، Stream موردنظر گسترش خواهد یافت. برای استفاده از این متد، Stream مورد نظر باید قابلیت Writing و Seeking را داشته باشد.
پراپرتی فقط خواندنی که طول Stream را مشخص میکند. در صورتیکه Stream مورد نظر Seekable باشد، میتوان از این پراپرتی بهر برد؛ این بدین معنی است که اگر با یک Stream از نوع non-seekable کار میکنید، در صورت استفاده از این خصوصیت، تمام بایتهای Stream خوانده شده و بعد از قرار گرفتن در یک buffer (به عنوان مثال در memory)، محاسبه خواهد شد.
Position
پراپرتی برای خواندن یا تنظیم مکان فعلی Pointer مربوط به Stream، میباشد. برای استفاده از آن لازم است Stream مورد استفاده Seekable باشد.
مشخص میکند که Stream مورد استفاده Seekable می باشد یا خیر.
به طور خلاصه با استفاده از متد Seek انعطاف پذیری بالایی خواهید داشت. با مقدار دهی پراپرتی Position، این مقدار همیشه نسبت به ابتدای Stream در نظر گرفته خواهد شد (شکل زیر)؛ این در حالی است که با استفاده از متد Seek میتوان مشخص کرد که مقدار Offset تنظیم شده نسبت به ابتدا، مکان جاری و یا انتهای Stream میباشد.
مثال:
using (FileStream fs = File.Create(@"C:\files\testfile3.txt")) { // position is 0 long pos = fs.Position; // sets the position to 1 fs.Position = 1; byte[] arrbytes = { 100, 101 }; //writes the content of arrbytes into current position - which is 1 fs.Write(arrbytes, 0, arrbytes.Length); //position is now 3 as its advanced by write pos = fs.Position; fs.Position = 0; byte[] readdata1 = ReadBytes(fs); }
Closing and Flushing
کلاس پایه System.IO.Stream اینترفیس IDisposable را پیاده سازی کرده است؛ لذا بهتر است برای آزاد سازی منابع از جمله: file handle در FileStream یا socket handle در NetworkStream، بعد از استفاده، متد Dispose آنها را فراخوانی کنید یا با وهله سازی آنها در بدنه using، این فراخوانی به صورت ضمنی انجام شود.
نکته: باید توجه کنید که با Close (معادل Dispose) شدن decorator streamها ، backing store stream داخلی آنها نیز Close خواهد شد.
با توجه به اینکه I/O عملیات پرهزینهای میباشد، برخی از انواع Streamها به منظور بهبود کارآیی از یک مکانیزم بافر داخلی استفاده میکنند. به این شکل که عملیات Write، داده را به جای آنکه درون backing store ذخیره سازی کند، درون این بافر ذخیره سازی خواهد کرد. زمانیکه این بافر پر شود یا به صورت صریح متدهای Flush یا Close فراخوانی شده باشند، داده موجود در بافر درون backing store ذخیره خواهد شد. در نتیجه عملیات Read هم میتواند به بخشی از داده اصلی که هم اکنون درون بافر میباشد، دسترسی سریعتری داشته باشد. به عنوان مثال FileStream از این مکانیزم داخلی برخوردار است. سایز پیش فرض این بافر 4KB (قابل تنظیم است) میباشد. برای سایر مواردی که این امکان برایشان وجود ندارد، میتوان از BufferedStream برای Decorate کردن Stream مورد نظر خود استفاده کرد.
نکته: به صورت پیش فرض، Streamها thread-safe نیستند و امکان خواندن و نوشتن همزمان توسط چند thread برروی یک stream مشترک را نخواهید داشت. برای حل این موضوع، متد استاتیکی در کلاس Stream تحت عنوان Synchronized در نظر گرفته شده است که یک thread-safe wrapper را به برروی stream ورودی در نظر گرفته و آن را به عنوان خروجی برگشت خواهد داد.
[HostProtection(SecurityAction.LinkDemand, Synchronization = true)] public static Stream Synchronized(Stream stream) { if (stream == null) throw new ArgumentNullException("stream"); if (stream is Stream.SyncStream) return stream; return (Stream) new Stream.SyncStream(stream); }
استفاده از Froala WYSIWYG Editor در ASP.NET
- سایر نظرات تکمیلی و نگارشهای بهبود یافته را هم مدنظر داشته باشید.
بهبود SEO در ASP.NET MVC
SFDown
این فرم دارای اطلاعاتی شامل : نام، جنسیت ، زمینههای کاری، کشور، تاریخ تولد و تصویر میباشد.
TextBlock
همان Label قدیمی خودمان است که برای نمایش متون کاربر دارد. متن داخل آن بین دو تگ قرار میگیرد و یا از خاصیت Text آن کمک گرفته خواهد شد. حتما از خاصیت Width و height آن برای مقداردهی کمک بگیرید، زیرا در غیر آن صورت کل Container خود را خواهد پوشاند. در صورتی که متنی در مکان خود جا نشود میتوان از دو ویژگی استفاده کرد. آن را برش داد یا به خطوط بعدی شکست. برای حذف یا برش باقی مانده متن میتوان از خصوصیت TextTrimming استفاده کرد که سه مقدار میگیرد:
None مقدار پیش فرض
CharacterEllipsis با نزدیک شدن به آخر پهنای کار از ... استفاده مینماید. در صورتی که لیستی یا مورد مشابهی دارید میتواند بسیار کاربردی باشد.
WordEllipsis این گزینه هم مانند مورد بالاست با این تفاوت که سعی دارد تا آنجا که ممکن است خود را به آخرین حرف کلمه برساند تا شکستگی در وسط کلمه اتفاق نیفتد و آخرین کلمه کامل دیده شود و بعد ... قرار بگیرد؛ هر چند در تستهای خودم تفاوتی مشاهده نکردم.
گزینه TextWrapping جهت شکستن یک خط به خطوط است؛ موقعی که متن شما به انتهای صفحه میرسد، این ویژگی باعث میشود متن به بیرون از پنجره نرفته و یک خط به سمت پایین حرکت کند. این گزینه سه مقدار را دارد:
تصویر زیر حالت اصلی نمایش بدون نیاز به Wrap شدن است:
None: مقدار پیش فرض که خصوصیت Wrap را به همراه ندارد.
Wrap: فعال سازی ویژگی TextWrapping
WrapWithOverflow: فرق این گزینه با گزینه بالا در این است که ، گزینه بالا در هر موقعیتی که پیش بیاید عمل خط شکن را روی عبارت یا حتی آن کلمه انجام میدهد. ولی در این گزینه فرصت خط شکنی مثل بالا فراهم نیست و اگر روی کلمهای خط شکنی رخ دهد، مابقی آن کلمه از ناحیهی خودش خارج شده و از کلمهی بعدی، خط شکنی صورت میگیرد.
خصوصیت LineStackingStrategy:
این خصوصیت فاصلهی بین خطوط را با استفاده از یک واحد منطقی dp مشخص میکند. هر چند دو گزینه دیگر هم دارد که دو تصویر زیر را در این صفحه به شما نمایش میدهد:
برای ساخت فرم از یک گرید با سه ستون و 6 سطر استفاده میکنم.
<Grid Margin="5"> <Grid.ColumnDefinitions> <ColumnDefinition Width="Auto"></ColumnDefinition> <ColumnDefinition Width="*"></ColumnDefinition> <ColumnDefinition Width="Auto"></ColumnDefinition> </Grid.ColumnDefinitions> <Grid.RowDefinitions> <RowDefinition Height="Auto"></RowDefinition> <RowDefinition Height="Auto"></RowDefinition> <RowDefinition Height="Auto"></RowDefinition> <RowDefinition Height="Auto"></RowDefinition> <RowDefinition Height="Auto"></RowDefinition> <RowDefinition Height="Auto"></RowDefinition> </Grid.RowDefinitions> </Grid>
در داخل گرید بعد از تعریف سطر و ستون، همانطور که قبلا توضیح دادیم کنترلهای TextBlock را اضافه میکنیم:
<TextBlock Grid.Column="0" Grid.Row="0" VerticalAlignment="Center" HorizontalAlignment="Left" >Name</TextBlock> <TextBlock Grid.Column="0" Grid.Row="1" VerticalAlignment="Center" HorizontalAlignment="Left" >Gender</TextBlock> <TextBlock Grid.Column="0" Grid.Row="2" VerticalAlignment="Center" HorizontalAlignment="Left" >Field Of Work</TextBlock> <TextBlock Grid.Column="0" Grid.Row="3" VerticalAlignment="Center" HorizontalAlignment="Left" >Country</TextBlock> <TextBlock Grid.Column="0" Grid.Row="4" VerticalAlignment="Center" HorizontalAlignment="Left" >Birth Date</TextBlock> <TextBox Grid.Row="0" Grid.Column="1" Name="Txtname" HorizontalAlignment="Left" Margin="5" Width="200" ></TextBox>
Buttons
برای فیلد جنسیت Gender هم از RadioButton کمک گرفتم که با استفاده از خاصیت GroupName میتوان دستهای از این کنترلها را با هم مرتبط ساخت تا با انتخاب یک آیتم جدید از همان گروه، آیتم قبلی که انتخاب شده بود از حالت انتخاب خارج شده و آیتم جدیدی انتخاب شود. از خاصیت IsChecked میتوان برای انتخاب یک آیتم بهره برد.
به صورت کلی دکمهها به چند دسته زیر تقسیم میشوند:
- Button
- ToggleButton
- CheckBox
- RadioButton
که همگی این عناصر از کلاسی به نام ButtonBase مشتق شده اند. کد زیر RadioButtonها را به صورت عمودی چینش کرده است:
<StackPanel Orientation="Vertical" Grid.Row="1" Grid.Column="1" Margin="10"> <RadioButton GroupName="Gender" Name="RdoMale" IsChecked="True" >Male</RadioButton> <RadioButton GroupName="Gender" Name="RdoFemale" Margin="0 5 0 0" >Female</RadioButton> </StackPanel>
<StackPanel Orientation="Horizontal" Grid.Row="2" Grid.Column="1" Margin="10"> <CheckBox Name="ChkActor" >Actor/Actress</CheckBox> <CheckBox Name="ChkDirector" >Director</CheckBox> <CheckBox Name="ChkProducer" >Producer</CheckBox> </StackPanel> <ListBox Grid.Row="3" Grid.Column="1" Margin="10" Height="80"> <ListBoxItem> <TextBlock>UnitedStates</TextBlock> </ListBoxItem> <ListBoxItem> <TextBlock >UK</TextBlock> </ListBoxItem> <ListBoxItem> <TextBlock >France</TextBlock> </ListBoxItem> <ListBoxItem> <TextBlock >Japan</TextBlock> </ListBoxItem> </ListBox> <Calendar Grid.Row="4" Grid.Column="1" HorizontalAlignment="Left" Margin="10"></Calendar>
<Grid Grid.Row="0" Grid.Column="2" Grid.RowSpan="4"> <Grid.RowDefinitions> <RowDefinition Height="*"></RowDefinition> </Grid.RowDefinitions> <Grid.ColumnDefinitions> <ColumnDefinition Width="*"></ColumnDefinition> </Grid.ColumnDefinitions> <Image HorizontalAlignment="Right" Source="man.jpg" Stretch="UniformToFill" VerticalAlignment="Top" Width="100" Height="150"></Image> <Button Width="25" Height="15" Padding="0" HorizontalAlignment="Left" VerticalAlignment="Bottom" Margin="0,0,0,83"> <TextBlock VerticalAlignment="Center" Margin="0 -7 0 0">...</TextBlock> </Button> </Grid>
None: تصویر، اندازهی اصلی خود را حفظ کرده و هر مقدار آن که در کنترل جا شود، نمایش مییابد و بسته به سایز تصویر ممکن است گوشه هایی از تصویر نمایش نیابد.
Fill: تصویر را داخل کنترل به زور جا داده تا پهنا و ارتفاع عکس، هم اندازه کنترل میشود.
Uniform: تصویر بزرگ را با در نظر گرفتن نسبت پهنا و ارتفاع تصویر، با یکدیگر در کنترل جا میدهد.
UniformToFill: تصویر، کل کنترل را میگیرد ولی نسبت پهنا و عرض را حفظ کرده ولی قسمت هایی از تصویر در کنترل دیده نمیشود.
همانطور که قبلا هم گفتیم، خود کنترل دکمه شامل زیر کنترلهایی میشود که یکی از آنها TextBlock است و از طریق خصوصیت *.TextBlock دیگر خصوصیات آن قابل تنظیم است و البته برای خصوصی سازی بیشتر هم میتوان یک TextBlock را به صورت Nested یعنی داخل تگ Button تعریف کنید که ما همین کار را کرده ایم.
فرم نهایی ما به صورت زیر است:
در صورتی که دوست دارید جهت ListBox را از عمودی به افقی تغییر دهید میتوانید از پنلهای Stack یا Wrap استفاده کنید که تعریف آن به شکل زیر است:
<ListBox> <ListBox.ItemsPanel> <ItemsPanelTemplate> <VirtualizingStackPanel Orientation="Horizontal" /> </ItemsPanelTemplate> </ListBox.ItemsPanel> </ListBox>
بدین صورت ListBox به شکل زیر تغییر مییابد:
و در صورتی که میخواهید Scroll حذف شود و از Wrap استفاده کنید، کد را به شکل زیر تعریف کنید. فراموش نکنید که اسکرول افقی را غیرفعال کنید؛ وگرنه نتیجه کار به شکل بالا خواهد بود.
<ListBox ScrollViewer.HorizontalScrollBarVisibility="Disabled"> <ListBox.ItemsPanel> <ItemsPanelTemplate> <WrapPanel /> </ItemsPanelTemplate> </ListBox.ItemsPanel> </ListBox>
Calendar
تقویم یکی دیگر از کنترلهای موجود است که شامل خصوصیات زیر است:
DisplayDate: تاریخ پیش فرض و اولیه تقویم را مشخص میکند؛ در صورتی که ذکر نشود تاریخ جاری درج میشود.
<Calendar DisplayDate="01.01.2010" />
<Calendar DisplayDateStart="01.01.2015" DisplayDateEnd="05.01.2015" />
SelectionMode: نحوهی انتخاب تاریخ را مشخص میکند:
SingleDate: فقط یک تاریخ قابل انتخاب است.
SingleRange: میتوانید از یک تاریخ تا تاریخ دیگر را انتخاب کنید. ولی نمیتوانید مجددا چند انتخاب دیگر را در جای جای تقویم داشته باشید. مثلا از تاریخ 5 آپریل تا 10 آپریل را انتخاب کردهاید؛ ولی دیگر نمیتوانید تاریخ 15 آپریل یا محدودهی 15 آپریل تا 20 آپریل را انتخاب کنید. چون تنها قادر به انتخاب یک رنج یا محدوده تاریخی هستید.
MultipleRanges: بر خلاف گزینهی بالایی هر محدوده تاریخی قابل انتخاب است.
<Calendar SelectionMode="MultipleRange" />
نکته بعدی در مورد غیرفعال کردن بعضی از تاریخ هاست که شما قصد ندارید به کاربر اجازه دهید آنها را انتخاب کند. برای مثال تاریخهای 1 آپریل تا 10 آپریل را از دسترس خارج کنید. برای همین از خصوصیت BlackoutDates استفاده میکنیم که نحوهی تعریف آن به شرح زیر است که در این کد دو محدودهی تاریخی غیر فعال شده اند:
<Calendar> <Calendar.BlackoutDates> <CalendarDateRange Start="01/01/2010" End="01/06/2010" /> <CalendarDateRange Start="05/01/2010" End="05/03/2010" /> </Calendar.BlackoutDates> </Calendar>
DisplayMode : به طور پیش فرض، تقویم ماهها را نشان میدهد ولی میتوانید آن را توسط این خصوصیت روی سال یا دهه و ماه هم تنظیم کنید.
با انتخاب سال Year، تقویم ماههای یک سال را نمایش میدهد.
با انتخاب دهه Decades سالهای یک دههی تعیین شده را نشان میدهد و با انتخاب ماه Month روزهای هر ماه در آن نمایش داده میشود.
در هنگام انتخاب این گزینه، به داخل تقویم نگاه نکنید، بلکه به سر تیتر آن نگاه کنید.
<Calendar DisplayMode="Year" />
کلیات پروژه
پیاده سازی
var jsonFormatter = config.Formatters.OfType<JsonMediaTypeFormatter>().First(); jsonFormatter.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
var angularExample = angular.module('angularexample', ["restangular"]) angularExample.config(["RestangularProvider", function (RestangularProvider) { //RestangularProvider.setRestangularFields({ // id: "id" //}); RestangularProvider.setBaseUrl('/api'); }]); angularExample.controller("MainCtrl", ["Restangular", "$scope", function (Restangular, $scope) { var resource = Restangular.all('books'); resource.getList().then(function (response) { $scope.books = response; }); $scope.add = function () { resource.post($scope.newBook).then(function (newResource) { $scope.books.push(newResource); }) } }]);
<!DOCTYPE html> <html ng-app="angularexample" xmlns="http://www.w3.org/1999/xhtml"> <head> <title>Rest Angular Sample</title> <script src="//ajax.googleapis.com/ajax/libs/angularjs/1.1.5/angular.min.js"></script> <script src="http://cdn.jsdelivr.net/underscorejs/1.5.1/underscore-min.js"></script> <script src="/Scripts/restangular.min.js"></script> <script src="/app/app.js"></script> </head> <body> <div ng-controller="MainCtrl"> <div ng-repeat="item in books"> name is: {{item.name}}<br /> change name: <input type="text" ng-model="item.name" /><button type="submit" ng-click="item.put();">update</button> </div> <div> add new: <br /> <input type="text" ng-model="newBook.name" /><button type="submit" ng-click="add()">add</button> </div> </div> </body>
var resource = Restangular.all('books'); resource.getList().then(function (response) { $scope.books = response; });
$scope.add = function () { resource.post($scope.newBook).then(function (newResource) { $scope.books.push(newResource); }) }
<div ng-repeat="item in books"> name is: {{item.name}}<br /> change name: <input type="text" ng-model="item.name" /><button type="submit" ng-click="item.put();">update</button> </div>
سورس پروژه: RestangularSample.rar
ویژگی | WCF | ASMX |
حداقل پیشنیاز | دات نت سه | دات نت یک |
هدف | جایگزینی یکپارچهی فناورهای قبلی شامل ASMX ، WSE ، MSMQ ، COM+ Eenterprise services و .NET Remoting | ارائه وب سرویس |
پروتکلهای پشتیبانی شده | HTTP TCP Named pipes MSMQ Custom UDP | HTTP only |
پشتیبانی از WS-* standards | بلی | خیر |
پشتیبانی از اطلاعات بایناری | بلی | خیر |
پشتیبانی از REST | بلی | خیر |
میزبانهای مهیا | در هر نوع برنامهی تهیه شده با دات 3 به بعد قابل میزبانی است، مانند یک برنامه کنسول، یک سرویس ویندوز ان تی و غیره. به این لیست IIS را هم میتوان اضافه کرد. | فقط IIS |
سرعت | WCF Services نسبت به ASMX Web Services از 25 تا 50 درصد سریعتر هستند + و + | |
نحوهی پاسخ دهی به درخواستها (یا ایجاد یک وهله جدید) | Singleton / private session / per call | per-call |
پشتیبانی از تراکنشها (transaction) | پشتیبانی تو کار + | خیر |
امنیت | پشتیبانی تو کار + | خودتان باید فکری برای این موضوع نمائید. |
بسط پذیری | بلی + | خیر |
مدت زمان یادگیری | حداقل یک ماه | یک روز! |
iTextSharp پایه کار با فایلهای PDF را ارائه میدهد اما ابزاری را جهت سادهتر سازی تولید فایلهای PDF به همراه ندارد؛ هر چند مثلا امکان تبدیل HTML به PDF را دارا است اما باید گفت: «تا حدودی البته». اگر نیاز باشد جدولی را ایجاد کنیم باید کد نویسی کرد، اگر نیاز باشد تصویری اضافه شود به همین ترتیب و الی آخر. البته این را هم باید در نظر داشت که کد نویسی انعطاف قابل توجهی را در اختیار برنامه نویس قرار میدهد؛ شاید به همین دلیل این روزها مباحث «Code first» بیشتر مورد توجه برنامه نویسها است، تا مباحث «Wizard first» یک دهه قبل!
اما باز هم داشتن یک طراح بد نیست و میتواند در کاهش مدت زمان تولید نهایی یک فایل PDF مؤثر باشد. برای این منظور میتوان از برنامهی رایگان و معروف Open office استفاده کرد. توسط آن میتوان یک فرم PDF را طراحی و سپس فیلدهای آنرا (این قالب تهیه شده را) با iTextSharp پر کرد. این مورد میتواند برای تهیه گزارشهایی که تهیه آنها با ابزارهای متداول گزارش سازی عموما میسر نیست، بسیار مناسب باشد.
طراحی یک فرم PDF با استفاده از برنامه Open Office
آخرین نگارش برنامه Open office را از اینجا میتوانید دریافت کنید و آنچنان حجمی هم ندارد؛ حدودا 154 مگابایت است.
پس از نصب و اجرای برنامه، حداقل به دو طریق میتوان یک فرم جدید را شروع کرد:
الف) آغاز یک XML Form document جدید در Open office سبب خواهد شد که نوارهای ابزار طراحی فرم، مانند قرار دادن TextBox ، CheckBox و غیره به صورت خودکار ظاهر شوند.
ب) و یا آغاز یک سند معمولی و سپس مراجعه به منوی View->Toolbars->Form Controls هم همان حالت را به همراه خواهد داشت.
در اینجا برای طراحی یک گزارش یا فرم جدید تنها کافی است همانند روشهای متداول تهیه یک سند معمولی رفتار کنیم و مواردی را که قرار است توسط iTextSharp مقدار دهی کنیم، با کنترلهای نوار ابزار Form آن بر روی صفحه قرار دهیم که نمونهی ساده آنرا در شکل زیر ملاحظه میکنید:
برای گزارشهای فارسی بهتر است Alignment یک کنترل به Right تنظیم شود و Border به حالت Without frame مقدار دهی گردد. نام این کنترل را هم بخاطر بسپارید و یا تغییر دهید. از این نامها در iTextSharp استفاده خواهیم کرد. (صفحه خواص فوق با دوبار کلیک بر روی یک کنترل قرار گرفته بر روی فرم ظاهر میشود)
مرحله بعد، تبدیل این فرم به فایل PDF است. کلیک بر روی دکمه تهیه خروجی به صورت PDF در نوار ابزار اصلی آن برای اینکار کفایت میکند. این گزینه در منوی File نیز موجود است.
فرمهای PDF تهیه شده در اینجا، فقط خواندنی هستند. مثلا یک کاربر میتواند آنها را پر کرده و چاپ کند. اما ما از آنها در ادامه به عنوان قالب گزارشات استفاده خواهیم کرد. بنابراین جهت ویرایش فرمهای تهیه شده بهتر است فایلهای اصلی Open Office مرتبط را نیز درجایی نگهداری کرد و هر بار پس از ویرایش، نیاز است تا خروجی جدید PDF آنها تهیه شود.
استفاده از iTextSharp جهت مقدار دهی فیلدهای یک فرم PDF
در ادامه میخواهیم این قالب گزارشی را که تهیه کردیم با کمک iTextSharp پر کرده و یک فایل PDF جدید تهیه کنیم. سورس کامل اینکار را در ذیل مشاهده میکنید:
using System;
using System.Diagnostics;
using System.IO;
using iTextSharp.text;
using iTextSharp.text.pdf;
namespace PdfForm
{
class Program
{
//روش صحیح ثبت و معرفی فونت در این کتابخانه
public static iTextSharp.text.Font GetTahoma()
{
var fontName = "Tahoma";
if (!FontFactory.IsRegistered(fontName))
{
var fontPath = Environment.GetEnvironmentVariable("SystemRoot") + "\\fonts\\tahoma.ttf";
FontFactory.Register(fontPath);
}
return FontFactory.GetFont(fontName, BaseFont.IDENTITY_H, BaseFont.EMBEDDED);
}
static void Main(string[] args)
{
string fileNameExisting = @"form.pdf";
string fileNameNew = @"newform.pdf";
using (var existingFileStream = new FileStream(fileNameExisting, FileMode.Open))
using (var newFileStream = new FileStream(fileNameNew, FileMode.Create))
{
var pdfReader = new PdfReader(existingFileStream);
using (var stamper = new PdfStamper(pdfReader, newFileStream))
{
//نکته مهم جهت کار با اطلاعات فارسی
//در غیراینصورت شاهد ثبت اطلاعات نخواهید بود
stamper.AcroFields.AddSubstitutionFont(GetTahoma().BaseFont);
//form.Fields.Keys = تمام فیلدهای موجود در فرم
var form = stamper.AcroFields;
//مقدار دهی فیلدهای فرم
form.SetField("TextBox1", "مقدار1");
form.SetField("TextBox2", "مقدار2");
// "Yes" and "Off" are valid values here
form.SetField("Check Box 1", "Yes");
// "" and "Off" are valid values here
form.SetField("Option Button 1", "");
// نحوه مقدار دهی لیست
form.SetListOption("ListBox1", new[] { "1مقدار یک", "مقدار دو1" }, null);
form.SetField("ListBox1", null);
// به این ترتیب فرم دیگر توسط کاربر قابل ویرایش نخواهد بود
//stamper.PartialFormFlattening --> جهت غیرقابل ویرایش نمودن فیلدی مشخص
stamper.FormFlattening = true;
stamper.Close();
pdfReader.Close();
}
}
Process.Start("newform.pdf");
}
}
}
توضیحات:
چون در اینجا فایل PDF، از پیش تهیه شده است، پس باید از اشیاء PdfReader و PdfStamper جهت خواندن و نوشتن اطلاعات در آنها استفاده کرد. سپس توسط شیء stamper.AcroFields میتوان به این فیلدها یا همان کنترلهایی که در برنامهی Open office بر روی فرم قرار دادیم، دسترسی پیدا کنیم.
در ابتدا نیاز است فونت این فیلدها توسط متد AddSubstitutionFont مقدار دهی شود. این مورد برای گزارشهای فارسی الزامی است؛ در غیراینصورت متنی را در خروجی مشاهده نخواهید کرد.
ادامه کار هم مشخص است. توسط متد form.SetField مقداری را به کنترلهای قرار گرفته بر روی فرم نسبت میدهیم. آرگومان اول آن نام کنترل است و آرگومان دوم، مقدار مورد نظر میباشد. اگر کنترل CheckBox را بر روی صفحه قرار دادید، تنها مقدارهای Yes و Off را میپذیرد (آن هم با توجه به اینکه به کوچکی و بزرگی حروف حساس است). اگر یک Radio button یا در اینجا Option button را بر روی فرم قرار دادید، تنها مقدارهای خالی و Off را قبول خواهد کرد. نحوهی مقدار دهی یک لیست هم در اینجا ذکر شده است.
در پایان چون نمیخواهیم کاربر نهایی قادر به ویرایش اطلاعات باشد، FormFlattening را true خواهیم کرد و به این ترتیب، کنترلها فقط خواندنی خواهند شد. البته اگر همانطور که ذکر شد، border کنترلها را در حین طراحی حذف کنید، PDF نهایی تولیدی یکپارچه و یک دست به نظر میرسد و اصلا مشخص نخواهد بود که این فایل پیشتر یک فرم قابل پر کردن بوده است.
در این پنل میتوان به اضافه ، ویرایش و حذف استایل هایی که به صفحهی جاری توسط فایلهای مختلف اضافه شده اند و یا داخل خود صفحه تعریف شده اند پرداخت.
همچنین در این پنل امکان ویرایش یک فایل css بصورت کامل وجود دارد ، به این صورت که میتوانید تمام محتویات فایل مورد نظر را در یک Text area ویرایش کنید.
مطابق با روالی که در قسمت قبل پیش گرفتیم عمل میکنیم و به ترتیب به تشریح ابزار پنل ، خود پنل ، منوی راست کلیک و پنل جانبی ِ Elements میپردازیم.
Options Menu
با راست کلیک کردن بروی تب CSS و یا کلیک کردن بروی فلش کوچک روی تب CSS قابل دسترس است و امکانات زیر را محیا میکند:
- Expand Shorthand Properties : نمایش دستورات css بصورت کامل ، به این معنی که دستوراتی مانند margin که هم بصورت خلاصه و هم بصورت کامل تعریف میشوند را بصورت کامل نمایش میدهد. مثلا margin را چهار مقدار margin-top , margin-right , margin-bottom , margin-left نمایش میدهد.
- Color As Hex , Color As RGB , Color As HSL : با انتخاب یکی از سه مقدار ذکر شده ، فرمت نمایش رنگها به حالت انتخاب شده تغییر میکند.
مثلا دستور color : #000000 به این صورت نمایش داده میشود : color : rgb(0, 0, 0) - Refresh : این گزینه محتویات پنل را بروز میکند.
Panel Toolbar
ابزاری موجود در این پنل مشابه پنلهای دیگر در بالای پنل و در زیر تب پنلها قرار دارد و شامل ابزارهای زیر میشود:
- Edit Button : این ابزار به دو صورت Live Edit و Source Edit در دسترس هست که با کلیک بروی فلش کوچکی که در سمت راست دکمه قرار دارد میتوان نوع آن را تغییر داد.
انتخاب حالت Source Edit ، باعث میشود سورس فایل به همان صورتی که به مرورگر ارسال شده نمایش داده شود. ( کامنتها ، فرمت فایل و ... حفظ میشود. )
حالت Live Edit هم باعث نمایش محتویات فایل بصورت مرتب شده میشود. ( کامنتها و دستوراتی که توسط مرورگر تفسیر نشده اند نمایش داده نمیشوند. ) - CSS Location Menu : نام فایل جاری که در پنل در حال نمایش است را نمایش میدهد و همچنین با کلیک بروی آن ، فایلهای استایل دیگری که در صفحه بارگزاری شده اند را نمایش میدهد. با کلیک بروی هرکدام از فایلهای نمایش داده شده ، همان فایل در پنل باز میشود.
استایل هایی که در خود صفحه تعریف شده اند با نام خود صفحه در این قسمت نمایش داده میشوند و اگر استایلهای در چندین تگ style تعریف شده باشند ، دومین تگ با #2 ، سومین با #3 و ... مشخص میشوند.
هنگام باز بودن :
1- فایل هایی که از پوشههای مختلف در صفحه بارگذاری شده اند ، با نام پوشه از هم تفکیک شده اند:
2- با تایپ کردن عبارت دلخواه میتوان نتایج را محدود کرد:
Panel
منظور از پنل ، قسمتی هست که استایل بصورت فرمت شده و نمایش داده شده است.
Infotips
توسط این قابلیت ، زمانی که موس را بروی آدرس تصاویر ، نوع فونت و ... ببرید ، پاپ آپ کوچکی باز میشود و اطلاعاتی در مورد مقادیر میدهد. مثلا اینکه آیا تصویر مورد نظر به درستی بارگذاری شده یا نه ، نمای کوچکی از تصویر ، شکل فونت و ...
Editing rules
برای ویرایش تعاریف CSS شامل Selectorها ، دستورات و مقادیر ، این پنل ابزارهای مفیدی ارائه میکند.
برای ویرایش یک selector ، دستور یا مقدار آن بروی آن عبارت کلیک کنید ، در همین حال یک text box ظاهر میشود و میتوانید مقدار جدید را وارد کنید. پس از انجام ویرایش مورد نظر بروی قسمتی از صفحه کلیک کنید یا کلید Tab سپس Esc را بزنید. برای ویرایش دستورات بعدی ، کلید Tab را بزنید و برای لغو تغییر جاری ، کلید Esc را بزنید.
برای ایجاد یک rule جدید ، بروی قسمتی از پنل راست کلیک کرده و سپس گزینهی "New Rule..." را برگزینید.
برای ایجاد یک property هم چند راه وجود دارد:
- بروی قسمت از فضای خالی تعریف یک استایل دوبار کلیک کنید.
- در قسمتی از تعریف یک استایل راست کلیک کرده و گزینهی "New Property..." را انتخاب کنید.
- بروی مقدار آخرین property کلیک کرده و کلید Tab را بزنید.
هنگام ویرایش یا ایجاد یک Rule , Propery یا مقدار بصورت inline ، حاشیهی text box متناسب با مقدار وارد شده رنگی را که نمایانگر حالت ذخیرهی مقدار وارد شده است نمایش میدهد. برای مثال اگر مقداری که برای یک selector وارد شده است نامعتبر باشد ، رنگ حاشیهی text box قرمز میشود.
جدول زیر حالتهای مختلف را شرح میدهد:
رنگ حاشیه | Selectorها | نام Properyها | مقادیر Propertyها و Ruleها |
خاکستری | تغییری ایجاد نشده است | تغییری ایجاد نشده است | تغییری ایجاد نشده است |
قرمز | selector نامعتبر است | نام نامعتبر است | مقدار نامعتبر است |
زرد | selector صحیح است اما بروی Element فعلی تاثیر ندارد | نام صحیح است اما مقدار Property غیر مجاز است یا وارد نشده است | |
سبز | selector صحیح است | نام و مقدار صحیح هستند | مقدار صحیح است |
Auto-completion
زمانی که در حال ایجاد/ویراش کردن یک Rule, Propery یا مقدار آنها هستید ، میتوانید از این قابلیت استفاده کنید. مثلا با وارد کردن # تمام Selector هایی که میتوانند با # شروع شوند در دسترس شما هستند و با دکمههای Up/Down میتوانید مقادیر ممکن را مرور کنید.
اگر در حال ویرایش یک مقدار عددی هستید ، میتوانید با دکمههای Up/Down مقادیر را یک واحد یک واحد افزایش دهید. با نگه داشتن کلید Shift و فشردن Up/Down میتوانید مقادیر را 10تا 10تا تغییر دهید و با نگه داشتن Ctrl بجای Sihft ، یک دهم یک دهم.
Toggling styles
با این امکان میتوانید یک Property را بطور موقت فعال/غیرفعال کنید. با حرکت موس از یک Property یک آیکون قرمز رنگ در کنار آن نمایش داده میشود که با کلیک بروی آن ، Property و مقدار آن کمرنگ شده و آیکون قرمز رنگ کنار آن ثابت میشود. با کلیک مجدد بروی آن ، Property فعال میشود.
Context Menu
این منو زمانی که در پنل راست کلیک کنید ظاهر میشود و نسبت به منطقه (Context)ای که در آن راست کلیک کرده اید ، گزینههای متفاوتی را مشاهده خواهید کرد. در جدول زیر ، گزینهها ، Contextشان و توضیح هر گزینه آمده است.
گزینه | Context | توضیحات |
Copy Location | CSS Location Menu | آدرس فایل استایل را در حافظه کپی میکند. |
Open in New Tab | CSS Location Menu | فایل استایل را در یک تب جدید باز میکند. |
Copy Image Location | image values | آدرس تصویر را در حافظه کپی میکند. |
Open Image in New Tab | image values | تصویر را در یک تب جدید باز میکند. |
Copy Color | color values | مقدار رنگ را در حافظه کپی میکند. |
Copy Rule Declaration | CSS selector | Selector و Propertyها را در حافظه کپی میکند. |
Copy Style Declaration | CSS selector | فقط Propertyها را در حافظه کپی میکند. |
New Rule... | همه جای پنل | یک Rule جدید بالای قسمتی که راست کلیک شده ایجاد میکند. |
Delete "<selector>" | CSS selector | Rule را حذف میکند. |
New Property... | CSS rule | یک Property جدید در Ruleی که در آن راست کلیک شده ایجاد میکند. |
Edit "<property name>"... | CSS property | Property فعلی به حالت ویرایش درمی آید. ( راه سادهتر ، کلیک بروی Property است. ) |
Delete "<property name>"... | CSS property | Property فعلی را حذف میکند. |
Disable "<property name>"... | CSS property | Property فعلی را غیرفعال میکند. |
Refresh | همه جای پنل | محتویات پنل را بروز رسانی میکند. |
Inspect in DOM panel | CSS Location Menu, CSS rule | فایل استایل یا Rule را در پنل DOM باز میکند. |
Elements Side Panel
در این پنل که سمت راست پنل CSS قرار دارد ، با وارد کردن یک CSS Selector میتوانید Elementهایی که در صفحه با آن مطابقت دارند را مشاهده کنید.
برای وارد کردن CSS Selector هم میتوان مقدار مورد نظر را در قسمت Try a selector... وارد کرد هم میتوان بروی یکی از Selectorهای پنل راست کلیک کرد و گزینهی Get Matching Elements را برگزید.
Context Menu این قسمت هم مشابه Context Menu پنل HTML هست و فقط در ورژن 1.11 گزینهی Paste HTML اضافه شده که در این کامنت از مقالهی آموزش فایرباگ - #5 - HTML Panel توضیح داده شده است.