PM> Install-Package EFInteractiveViews
private static bool _isPreGeneratedViewCacheSet; private void InitializationPreGeneratedViews() { if (_isPreGeneratedViewCacheSet) return; var precompiledViewsFilePath = new FileInfo(Assembly.GetExecutingAssembly().Location).DirectoryName + @”\EF6PrecompiledViews.xml”; InteractiveViews.SetViewCacheFactory(this, new FileViewCacheFactory(precompiledViewsFilePath)); _isPreGeneratedViewCacheSet = true; }
{ "compilerOptions": { "strict": true, "removeComments": false, "sourceMap": false, "noEmitOnError": true, "target": "ES2020", "module": "ES2020", "outDir": "wwwroot/scripts" }, "include": [ "Scripts/**/*.ts" ], "exclude": [ "node_modules" ] }
<Project Sdk="Microsoft.NET.Sdk.Razor"> <ItemGroup> <PackageReference Include="Microsoft.TypeScript.MSBuild" Version="4.3.5"> <PrivateAssets>all</PrivateAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> </PackageReference> </ItemGroup> <ItemGroup> <Content Remove="tsconfig.json" /> </ItemGroup> <ItemGroup> <TypeScriptCompile Include="tsconfig.json"> <CopyToOutputDirectory>Never</CopyToOutputDirectory> </TypeScriptCompile> </ItemGroup> </Project>
window.exampleJsFunctions = { showPrompt: function (message) { return prompt(message, 'Type anything here'); } };
namespace JSInteropWithTypeScript { export class ExampleJsFunctions { public showPrompt(message: string): string { return prompt(message, 'Type anything here'); } } } export function showPrompt(message: string): string { var fns = new JSInteropWithTypeScript. ExampleJsFunctions(); return fns.showPrompt(message); }
private const string ScriptPath = "./_content/----namespace-here---/scripts/file.js"; private IJSObjectReference scriptModule;
protected override async Task OnAfterRenderAsync(bool firstRender) { if (scriptModule == null) scriptModule = await JSRuntime.InvokeAsync<IJSObjectReference>("import", ScriptPath);
await scriptModule.InvokeVoidAsync("exported fn name", params);
public partial class MyComponent : IAsyncDisposable
public async ValueTask DisposeAsync() { if (scriptModule != null) { await scriptModule.DisposeAsync(); } }
قسمت اول این بحث و همچنین پیشنیاز آنرا در اینجا و اینجا میتوانید مطالعه نمائید.
همهی اینها بسیار هم نیکو! اما ... آیا واقعا باید به ازای هر روال رویدادگردانی یک Attached property نوشت تا بتوان از آن در الگوی MVVM استفاده کرد؟ برای یکی دو مورد شاید اهمیتی نداشته باشد؛ اما کم کم با بزرگتر شدن برنامه نوشتن این Attached properties تبدیل به یک کار طاقت فرسا میشود و اشخاص را از الگوی MVVM فراری خواهد داد.
برای حل این مساله، تیم Expression Blend راه حلی را ارائه دادهاند به نام Interaction.Triggers که در ادامه به توضیح آن پرداخته خواهد شد.
ابتدا نیاز خواهید داشت تا SDK مرتبط با Expression Blend را دریافت کنید: (^)
سپس با فایل System.Windows.Interactivity.dll موجود در آن کار خواهیم داشت.
یک مثال عملی:
فرض کنید میخواهیم رویداد Loaded یک View را در ViewModel دریافت کنیم. زمان وهله سازی یک ViewModel با زمان وهله سازی View یکی است، اما بسته به تعداد عناصر رابط کاربری قرار گرفته در View ، زمان بارگذاری نهایی آن ممکن است متفاوت باشد به همین جهت رویداد Loaded برای آن درنظر گرفته شده است. خوب، ما الان در ViewModel نیاز داریم بدانیم که چه زمانی کار بارگذاری یک View به پایان رسیده.
یک راه حل آنرا در قسمت قبل مشاهده کردید؛ باید برای این کار یک Attached property جدید نوشت چون نمیتوان Command ایی را به رویداد Loaded انتساب داد یا Bind کرد. اما به کمک امکانات تعریف شده در System.Windows.Interactivity.dll به سادگی میتوان این رویداد را به یک Command استاندارد ترجمه کرد:
<Window x:Class="WpfEventTriggerSample.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:vm="clr-namespace:WpfEventTriggerSample.ViewModels"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<vm:MainWindowViewModel x:Key="vmMainWindowViewModel" />
</Window.Resources>
<Grid DataContext="{Binding Source={StaticResource vmMainWindowViewModel}}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Loaded">
<i:InvokeCommandAction Command="{Binding DoLoadCommand}"
CommandParameter="I am loaded!" />
</i:EventTrigger>
</i:Interaction.Triggers>
<TextBlock Text="Testing InvokeCommandAction..."
Margin="5" VerticalAlignment="Top" />
</Grid>
</Window>
ابتدا ارجاعی به اسمبلی System.Windows.Interactivity.dll باید به پروژه اضافه شود. سپس فضای نام xmlns:i باید به فایل XAML جاری مطابق کدهای فوق اضافه گردد. در نهایت به کمک Interaction.Triggers آن، ابتدا نام رویداد مورد نظر را مشخص میکنیم (EventName) و سپس به کمک InvokeCommandAction، این رویداد به یک Command استاندارد ترجمه میشود.
ViewModel این View هم میتواند به شکل زیر باشد که با کلاس DelegateCommand آن در پیشنیازهای بحث جاری آشنا شدهاید.
using WpfEventTriggerSample.Helper;
namespace WpfEventTriggerSample.ViewModels
{
public class MainWindowViewModel
{
public DelegateCommand<string> DoLoadCommand { set; get; }
public MainWindowViewModel()
{
DoLoadCommand = new DelegateCommand<string>(doLoadCommand, canDoLoadCommand);
}
private void doLoadCommand(string param)
{
//do something
}
private bool canDoLoadCommand(string param)
{
return true;
}
}
}
به این ترتیب حجم قابل ملاحظهای از کد نویسی Attached properties مورد نیاز، به سادهترین شکل ممکن، کاهش خواهد یافت.
بدیهی است این Interaction.Triggers را جهت تمام عناصر UI ایی که حداقل یک رویداد منتسب تعریف شده داشته باشند، میتوان بکار گرفت؛ مثلا تبدیل رویداد Click یک دکمه به یک Command استاندارد:
<Button>
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<i:InvokeCommandAction Command="{Binding DoClick}"
CommandParameter="I am loaded!" />
</i:EventTrigger>
</i:Interaction.Triggers>
</Button>
متدهای توکار استفاده از نوع دادهای XML - قسمت دوم
ابتدا جدول xmlTest را به همراه چند رکورد ثبت شده در آن، درنظر بگیرید:
CREATE TABLE xmlTest ( id INT IDENTITY PRIMARY KEY, doc XML ) GO INSERT xmlTest VALUES('<Person name="Vahid" />') INSERT xmlTest VALUES('<Person name="Farid" />') INSERT xmlTest VALUES('<Person name="Mehdi" /><Person name="Hamid" />') GO
استفاده از متد sql:column
در ادامه میخواهیم مقدار ویژگی name رکوردی را که نام آن Vahid است، به همراه id آن ردیف، توسط یک XQuery بازگشت دهیم:
SELECT doc.query(' for $p in //Person where $p/@name="Vahid" return <li>{data($p/@name)} has id = {sql:column("xmlTest.id")}</li> ') FROM xmlTest
در مورد متد data در قسمت قبل بیشتر بحث شد و از آن برای استخراج دادهی یک ویژگی در اینجا استفاده شدهاست. عبارات داخل {} نیز پویا بوده و به همراه سایر قسمتهای ثابت return، ابتدا محاسبه و سپس بازگشت داده میشود.
اگر این کوئری را اجرا کنید، ردیف اول آن مساوی عبارت زیر خواهد بود
<li>Vahid has id = 1</li>
یک روش برای حذف این ردیفهای خالی استفاده از متد exist است به شکل زیر:
SELECT doc.query(' for $p in //Person where $p/@name="Vahid" return <li>{data($p/@name)} has id = {sql:column("xmlTest.id")}</li> ') FROM xmlTest WHERE doc.exist(' for $p in //Person where $p/@name="Vahid" return <li>{data($p/@name)} has id = {sql:column("xmlTest.id")}</li> ')=1
روش دوم استفاده از یک derived table و بازگشت ردیفهای غیرخالی است:
SELECT * FROM ( (SELECT doc.query(' for $p in //Person where $p/@name="Vahid" return <li>{data($p/@name)} has id = {sql:column("xmlTest.id")}</li> ') AS col1 FROM xmlTest) ) A WHERE CONVERT(VARCHAR(8000), col1)<>''
استفاده از متد sql:variable
DECLARE @number INT = 1 SELECT doc.query(' for $p in //Person where $p/@name="Vahid" return <li>{data($p/@name)} has number = {sql:variable("@number")}</li> ') FROM xmlTest
استفاده از For XML برای دریافت یکبارهی تمام ردیفهای XML
اگر کوئری معمولی ذیل را اجرا کنیم:
SELECT doc.query('/Person') FROM xmlTest
اما اگر بخواهیم این سه ردیف را با هم ترکیب کرده و تبدیل به یک نتیجهی واحد کنیم، میتوان از For XML به نحو ذیل استفاده کرد:
DECLARE @doc XML SET @doc = (SELECT * FROM xmlTest FOR XML AUTO, ELEMENTS) SELECT @doc.query('/xmlTest/doc/Person')
بررسی متد xml.nodes
متد xml.nodes اندکی متفاوت است نسبت به تمام متدهایی که تاکنون بررسی کردیم. کار آن تجزیهی محتوای XML ایی به ستونها و سطرها میباشد. بسیار شبیه است به متد OpenXML اما کارآیی بهتری دارد.
DECLARE @doc XML =' <people> <person><name>Vahid</name></person> <person><name id="2">Farid</name></person> <person><name>Mehdi</name></person> <person><name>Hooshang</name><name id="1">Hooshi</name></person> <person></person> </people> '
در ادامه قصد داریم این اطلاعات را تبدیل به ردیفهایی کنیم که هر ردیف حاوی یک نام است. اولین سعی احتمالا استفاده از متد value خواهد بود:
SELECT @doc.value('/people/person/name', 'varchar(50)')
SELECT @doc.value('(/people/person/name)[1]', 'varchar(50)')
سعی بعدی استفاده از متد query است:
SELECT @doc.query('/people/person/name')
<name>Vahid</name> <name id="2">Farid</name> <name>Mehdi</name> <name>Hooshang</name> <name id="1">Hooshi</name>
الف) خروجی آن XML است.
ب) تمام اینها در طی یک ردیف و یک ستون بازگشت داده میشوند.
و این خروجی نیز چیزی نیست که برای ما مفید باشد. ما به ازای هر شخص نیاز به یک ردیف جداگانه داریم. اینجا است که متد xml.nodes مفید واقع میشود:
SELECT tab.col.value('text()[1]', 'varchar(50)') AS name, tab.col.query('.'), tab.col.query('..') from @doc.nodes('/people/person/name') AS tab(col)
هر ردیف حاصل از این جدول بازگشت داده شده، یک اشارهگر است. به همین جهت نمیتوان آنها را مستقیما نمایش داد. هر سطر آن، به نودی که با آن مطابق XQuery وارد شده تطابق داشته است، اشاره میکند. در اینجا مطابق کوئری نوشته شده، هر ردیف به یک نود name اشاره میکند. در ادامه برای استخراج اطلاعات آن میتوان از متد text استفاده کرد.
اگر قصد داشتید، اطلاعات کامل نود ردیف جاری را مشاهده کنید میتوان از
tab.col.query('.'),
روش دیگر بدست آوردن مقدار یک نود را در کوئری ذیل مشاهده میکنید؛ value دات و data دات. خروجی value مقدار آن نود است و خروجی data مقدار آن نود با فرمت XML.
SELECT tab.col.value('.', 'varchar(50)') AS name, tab.col.query('data(.)'), tab.col.query('.'), tab.col.query('..') from @doc.nodes('/people/person/name') AS tab(col)
همچنین اگر بخواهیم اطلاعات تنها یک نود خاص را بدست بیاوریم، میتوان مانند کوئری ذیل عمل کرد:
SELECT tab.col.value('name[.="Farid"][1]', 'varchar(50)') AS name, tab.col.value('name[.="Farid"][1]/@id', 'varchar(50)') AS id, tab.col.query('.') from @doc.nodes('/people/person[name="Farid"]') AS tab(col)
در مورد کار با جداول، بجای متغیرهای T-SQL نیز روال کار به همین نحو است:
DECLARE @tblXML TABLE ( id INT IDENTITY PRIMARY KEY, doc XML ) INSERT @tblXML VALUES('<person name="Vahid" />') INSERT @tblXML VALUES('<person name="Farid" />') INSERT @tblXML VALUES('<person />') INSERT @tblXML VALUES(NULL) SELECT id, doc.value('(/person/@name)[1]', 'varchar(50)') AS name FROM @tblXML
نکته : استفادهی وسیع SQL Server از XML برای پردازش کارهای درونی آن
بسیاری از ابزارهایی که در نگارشهای جدید SQL Server اضافه شدهاند و یا مورد استفاده قرار میگیرند، استفادهی وسیعی از امکانات توکار XML آن دارند. مانند:
Showplan، گرافهای dead lock، گزارش پروسههای بلاک شده، اطلاعات رخدادها، SSIS Jobs، رخدادهای Trace و ...
مثال اول: کدام کوئریها در Plan cache، کارآیی پایینی داشته و table scan را انجام میدهند؟
CREATE PROCEDURE LookForPhysicalOps (@op VARCHAR(30)) AS SELECT sql.text, qs.EXECUTION_COUNT, qs.*, p.* FROM sys.dm_exec_query_stats AS qs CROSS APPLY sys.dm_exec_sql_text(sql_handle) sql CROSS APPLY sys.dm_exec_query_plan(plan_handle) p WHERE query_plan.exist(' declare default element namespace "http://schemas.microsoft.com/sqlserver/2004/07/showplan"; /ShowPlanXML/BatchSequence/Batch/Statements//RelOp/@PhysicalOp[. = sql:variable("@op")] ') = 1 GO EXECUTE LookForPhysicalOps 'Table Scan' EXECUTE LookForPhysicalOps 'Clustered Index Scan' EXECUTE LookForPhysicalOps 'Hash Match'
اگر علاقمند هستید که اصل این اطلاعات را با فرمت XML مشاهده کنید، کوئری نوشته شده را تا پیش از where آن یکبار مستقلا اجرا کنید. ستون آخر آن query_plan نام دارد و حاوی اطلاعات XML ایی است.
مثال دوم: استخراج اپراتورهای رابطهای (RelOp) از یک Query Plan ذخیره شده
WITH XMLNAMESPACES(DEFAULT N'http://schemas.microsoft.com/sqlserver/2004/07/showplan') SELECT RelOp.op.value(N'../../@NodeId', N'int') AS ParentOperationID, RelOp.op.value(N'@NodeId', N'int') AS OperationID, RelOp.op.value(N'@PhysicalOp', N'varchar(50)') AS PhysicalOperator, RelOp.op.value(N'@LogicalOp', N'varchar(50)') AS LogicalOperator, RelOp.op.value(N'@EstimatedTotalSubtreeCost ', N'float') AS EstimatedCost, RelOp.op.value(N'@EstimateIO', N'float') AS EstimatedIO, RelOp.op.value(N'@EstimateCPU', N'float') AS EstimatedCPU, RelOp.op.value(N'@EstimateRows', N'float') AS EstimatedRows, cp.plan_handle AS PlanHandle, st.TEXT AS QueryText, qp.query_plan AS QueryPlan, cp.cacheobjtype AS CacheObjectType, cp.objtype AS ObjectType FROM sys.dm_exec_cached_plans cp CROSS APPLY sys.dm_exec_sql_text(cp.plan_handle) st CROSS APPLY sys.dm_exec_query_plan(cp.plan_handle) qp CROSS APPLY qp.query_plan.nodes(N'//RelOp') RelOp(op)
بررسی متد xml.modify
تا اینجا تمام کارهایی که صورت گرفت و نکاتی که بررسی شدند، به مباحث select اختصاص داشتند. اما insert، delete و یا update قسمتی از یک سند XML بررسی نشدند. برای این منظور باید از متد xml.modify استفاده کرد. از آن در عبارات update و یا set کمک گرفته شده و ورودی آن نباید نال باشد. در ادامه در طی مثالهایی این موارد را بررسی خواهیم کرد.
ابتدا فرض کنید که سند XML ما چنین شکلی را دارا است:
DECLARE @doc XML = ' <Invoice> <InvoiceId>100</InvoiceId> <CustomerName>Vahid</CustomerName> <LineItems> <LineItem> <Sku>134</Sku> <Quantity>10</Quantity> <Description>Item 1</Description> <UnitPrice>9.5</UnitPrice> </LineItem> <LineItem> <Sku>150</Sku> <Quantity>5</Quantity> <Description>Item 2</Description> <UnitPrice>1.5</UnitPrice> </LineItem> </LineItems> </Invoice> '
SET @doc.modify(' insert <InvoiceInfo><InvoiceDate>2014-02-10</InvoiceDate></InvoiceInfo> after /Invoice[1]/CustomerName[1] ') SELECT @doc
<Invoice> <InvoiceId>100</InvoiceId> <CustomerName>Vahid</CustomerName> <InvoiceInfo> <InvoiceDate>2014-02-10</InvoiceDate> </InvoiceInfo> <LineItems> ...
در SQL Server 2008 به بعد، امکان استفاده از متغیرهای T-SQL نیز در اینجا مجاز شدهاست:
SET @x.modify('insert sql:variable("@x") into /doc[1]')
The argument 1 of the XML data type method "modify" must be a string literal.
افزودن ویژگیهای جدید به یک سند XML توسط متد xml.modify
اگر بخواهیم یک ویژگی (attribute) جدید را به نود خاصی اضافه کنیم میتوان به نحو ذیل عمل کرد:
SET @doc.modify(' insert attribute status{"backorder"} into /Invoice[1] ') SELECT @doc
<Invoice status="backorder"> <InvoiceId>100</InvoiceId> ....
حذف نودهای یک سند XML توسط متد xml.modify
اگر بخواهیم تمام LineItemها را حذف کنیم میتوان نوشت:
SET @doc.modify('delete /Invoice/LineItems/LineItem') SELECT @doc
<Invoice status="backorder"> <InvoiceId>100</InvoiceId> <CustomerName>Vahid</CustomerName> <InvoiceInfo> <InvoiceDate>2014-02-10</InvoiceDate> </InvoiceInfo> <LineItems /> </Invoice>
به روز رسانی نودهای یک سند XML توسط متد xml.modify
اگر نیاز باشد تا مقدار یک نود را تغییر دهیم میتوان از replace value of استفاده کرد:
SET @doc.modify('replace value of /Invoice[1]/CustomerName[1]/text()[1] with "Farid" ') SELECT @doc
<Invoice status="backorder"> <InvoiceId>100</InvoiceId> <CustomerName>Farid</CustomerName> <InvoiceInfo> <InvoiceDate>2014-02-10</InvoiceDate> </InvoiceInfo> <LineItems /> </Invoice>
The target of 'replace value of' must be a non-metadata attribute or an element with simple typed content.
به روز رسانی نودهای خالی توسط متد xml.modify
باید دقت داشت، نودهای خالی (بدون مقدار)، مانند LineItems پس از delete کلیه اعضای آن در مثال قبل، قابل replace نیستند و باید مقادیر جدید را در آنها insert کرد. یک مثال:
DECLARE @tblTest AS TABLE (xmlField XML) INSERT INTO @tblTest(xmlField) VALUES ( '<Sample> <Node1>Value1</Node1> <Node2>Value2</Node2> <Node3/> </Sample>' ) DECLARE @newValue VARCHAR(50) = 'NewValue' UPDATE @tblTest SET xmlField.modify( 'insert text{sql:variable("@newValue")} into (/Sample/Node3)[1] [not(text())]' ) SELECT xmlField.value('(/Sample/Node3)[1]','varchar(50)') FROM @tblTest
UPDATE @tblTest SET xmlField.modify( 'replace value of (/Sample/Node3/text())[1] with sql:variable("@newValue")' )
کتابخانه iTextSharp دارای کلاسی است به نام HTMLWorker که کار تبدیل عناصر HTML را به عناصر متناظر خودش، انجام میدهد. این کلاس در حال حاضر منسوخ شده درنظر گرفته میشود (اینطور توسط نویسندگان آن اعلام شده) و دیگر توسعه نخواهد یافت. بنابراین اگر از HTMLWorker استفاده میکنید با یک کلاس قدیمی که دارای HTML Parser ایی بسیار بدوی است طرف هستید و در کل برای تبدیل محتوای HTML ایی با ساختار بسیار ساده بد نیست؛ اما انتظار زیادی از آن نداشته باشید.
جایگزین کلاس HTMLWorker در این کتابخانه در حال حاضر کتابخانه itextsharp.xmlworker است، که به صورت یک افزونه در کنار کتابخانه اصلی در حال توسعه میباشد. مشکل اصلی این کتابخانه، عدم پشتیبانی از UTF8 و راست به چپ است. بنابراین حداقل به درد کار ما نمیخورد.
راه حل بسیار بهتری برای موضوع اصلی بحث ما وجود دارد و آن هم استفاده از موتور WebKit (همان موتوری که برای مثال در Apple Safari استفاده میشود) برای HTML parsing و سپس تبدیل نتیجه نهایی به PDF است. پروژهای که این مقصود را میسر کرده، wkhtmltopdf نام دارد.
توسط آن به کمک موتور WebKit، کار HTML Parsing انجام شده و سپس برای تبدیل عناصر نهایی به PDF از امکانات کتابخانهای به نام QT استفاده میشود. کیفیت نهایی آن کپی مطابق اصل HTML قابل مشاهده در یک مرورگر است و با یونیکد و زبان فارسی هم مشکلی ندارد.
برای استفاده از این کتابخانهی native در دات نت، شخصی پروژهای را ایجاد کرده است به نام WkHtmlToXSharp که محصور کنندهی wkhtmltopdf میباشد. در ادامه به نحوه استفاده از آن خواهیم پرداخت:
الف) دریافت پروژه WkHtmlToXSharp
پروژه WkHtmlToXSharp را از آدرس زیر میتوانید دریافت کنید.
این پروژه به همراه فایلهای کامپایل شده نهایی wkhtmltopdf نیز میباشد و حجمی حدود 40 مگ دارد. به علاوه فعلا نسخه 32 بیتی آن در دسترس است. بنابراین باید دقت داشت که نباید تنظیمات پروژه دات نت خود را بر روی Any CPU قرار دهیم، زیرا در این حالت برنامه شما در یک سیستم 64 بیتی بلافاصله کرش خواهد کرد. تنظیمات target platform پروژه دات نتی ما حتما باید بر روی X86 تنظیم شود.
ب) پس از دریافت این پروژه و افزودن ارجاعی به اسمبلی WkHtmlToXSharp.dll، استفاده از آن به نحو زیر میباشد:
using System.IO; using WkHtmlToXSharp; using System; namespace Test2 { public class WkHtmlToXSharpTest { public static void ConvertHtmlStringToPdfTest() { using (var wk = new MultiplexingConverter()) { wk.Begin += (s, e) => Console.WriteLine("Conversion begin, phase count: {0}", e.Value); wk.Error += (s, e) => Console.WriteLine(e.Value); wk.Warning += (s, e) => Console.WriteLine(e.Value); wk.PhaseChanged += (s, e) => Console.WriteLine("PhaseChanged: {0} - {1}", e.Value, e.Value2); wk.ProgressChanged += (s, e) => Console.WriteLine("ProgressChanged: {0} - {1}", e.Value, e.Value2); wk.Finished += (s, e) => Console.WriteLine("Finished: {0}", e.Value ? "success" : "failed!"); wk.GlobalSettings.Margin.Top = "0cm"; wk.GlobalSettings.Margin.Bottom = "0cm"; wk.GlobalSettings.Margin.Left = "0cm"; wk.GlobalSettings.Margin.Right = "0cm"; wk.ObjectSettings.Web.EnablePlugins = false; wk.ObjectSettings.Web.EnableJavascript = false; wk.ObjectSettings.Load.Proxy = "none"; var htmlString = File.ReadAllText(@"c:\page.xhtml"); var tmp = wk.Convert(htmlString); File.WriteAllBytes(@"tst.pdf", tmp); } } } }
کار با وهله سازی از کلاس MultiplexingConverter شروع میشود. اگر علاقمند باشید که درصد پیشرفت کار به همراه خطاهای احتمالی پردازشی را ملاحظه کنید میتوان از رخدادگردانهایی مانند ProgressChanged و Error استفاده نمائید که نمونهای از آن در کد فوق بکارگرفته شده است.
تبدیل HTML به PDF آنچنان تنظیمات خاصی ندارد زیرا فرض بر این است که قرار است از همان تنظیمات اصلی HTML مورد نظر استفاده گردد. اما اگر نیاز به تنظیمات بیشتری وجود داشت، برای مثال به کمک GlobalSettings آن میتوان حاشیههای صفحات فایل نهایی تولیدی را تنظیم کرد.
موتور WebKit با توجه به اینکه موتور یک مرورگر است، امکان پردازش جاوا اسکریپت را هم دارد. بنابراین اگر قصد استفاده از آنرا نداشتید میتوان خاصیت ObjectSettings.Web.EnableJavascript را به false مقدار دهی کرد.
کار اصلی، در متد Convert انجام میشود. در اینجا میتوان یک رشته را که حاوی فایل HTML مورد نظر است به آن ارسال کرد و نتیجه نهایی، آرایهای از بایتها، حاوی فایل باینری PDF تولیدی است.
روش دیگر استفاده از این کتابخانه، مقدار دهی wk.ObjectSettings.Page میباشد. در اینجا میتوان Url یک صفحه اینترنتی را مشخص ساخت. در این حالت دیگر نیازی نیست تا به متد Convert پارامتری را ارسال کرد. میتوان از overload بدون پارامتر آن استفاده نمود.
یک نکته:
اگر میخواهید زبان فارسی را توسط این کتابخانه به درستی پردازش کنید، نیاز است حتما یک سطر زیر را به header فایل html خود اضافه نمائید:
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
ASP.NET MVC #23
1- تنظیم .* در iis5 با خطای wrong extension format مواجه میشود آیا راهی برای اصلاح آن وجود دارد.
2- اگر سیستم مسریابی را پسونددار کنیم چگونه به روش مناسبی میتوانیم همه جای پروژه را کنترل کنیم که مسیریابی دچار مشکل نشود ازجمله در area
3- چگونه بفهمیم که iis یکپارچه است یا کلاسیک
4 - آیا iis7 مد یکپارچه آن در ویندوز سرور 2003 و 2008 قابل نصب است
5-آیا برای ویندوز 8 تنظیم خاصی نیاز دارد . من یک مثال ساده را اجرا کردم و برنامه را بر روی iis قرار دادم با خطای 403 forbidden مربوط به صفحه آغازین مواجه شدم
6- طبق روش گفته شده در آدرس زیر نمیتوان یک صفحه آغازین دستی درست کرد و در iis تنظیم کرد مثلا deafualt.aspx و در لود آن مستقیما ادامه کار را به داخل مسیریابی mvc هدایت کرد
http://weblog.west-wind.com/posts/2013/Aug/15/IIS-Default-Documents-vs-ASPNET-MVC-Routes
EF Code First #3
حتی در حالت دستی هم پاورشل، اطلاعات را از DbContext دریافت و با ساختار بانک اطلاعاتی مقایسه میکند. سپس بر این اساس میتواند فایل SQL قابل اجرای بر روی بانک اطلاعاتی را تولید کند.
بهبود کارآیی IDEهای Jetbrains
سالها است که IDEهای Jetbrains برای اینکه در سکوهای کاری متفاوت قابل اجرا باشند، با جاوا نوشته میشوند. برای مثال Rider که یک IDE مخصوص دات نت است نیز با جاوا نوشته شدهاست و مابقی آنها نیز به همین صورت. اگر به مسیر C:\Program Files\JetBrains\JetBrains Rider 2018.3.4\bin\rider64.exe.vmoptions مراجعه کنید، فایل با پسوند vmoptions در حقیقت تنظیمات Java Virtual Machine یا JVM را به همراه دارد. این فایل طوری تنظیم شدهاست که کمترین منابع را مصرف کند؛ به همین جهت شاید در حین کار کردن با این IDEها احساس کنید که کند هستند. تنظیمات JVM مخصوص جاوای ویندوز و جاوای مک و لینوکس را در اینجا و اینجا میتوانید مطالعه کنید.
اگر بر این اساس فایل rider64.exe.vmoptions را بخواهیم تکمیل کنیم، میتوان به تنظیمات زیر رسید:
-Xms1024m -Xmx3072m -Xss64m -XX:ReservedCodeCacheSize=512m -XX:+UseCompressedOops -XX:NewRatio=2 -Dfile.encoding=UTF-8 -XX:+UseConcMarkSweepGC -XX:SoftRefLRUPolicyMSPerMB=250 -XX:NewSize=512m -XX:MaxNewSize=512m -XX:PermSize=512m -XX:MaxPermSize=1024m -XX:+UseParNewGC -XX:ParallelGCThreads=4 -XX:MaxTenuringThreshold=1 -XX:SurvivorRatio=8 -XX:+UseCodeCacheFlushing -XX:+AggressiveOpts -XX:+CMSClassUnloadingEnabled -XX:+CMSIncrementalMode -XX:+CMSIncrementalPacing -XX:+CMSParallelRemarkEnabled -XX:CMSInitiatingOccupancyFraction=65 -XX:+CMSScavengeBeforeRemark -XX:+UseCMSInitiatingOccupancyOnly -XX:-TraceClassUnloading -XX:+AlwaysPreTouch -XX:+TieredCompilation -XX:+DoEscapeAnalysis -XX:+UnlockExperimentalVMOptions -XX:LargePageSizeInBytes=256m -XX:+DisableExplicitGC -XX:+ExplicitGCInvokesConcurrent -XX:+PrintGCDetails -XX:+PrintFlagsFinal -XX:+CMSPermGenSweepingEnabled -XX:+UseAdaptiveGCBoundary -XX:+UseSplitVerifier -XX:CompileThreshold=10000 -XX:+OptimizeStringConcat -XX:+UseStringCache -XX:+UseFastAccessorMethods -XX:+UnlockDiagnosticVMOptions -ea -Dsun.io.useCanonCaches=false -Djava.net.preferIPv4Stack=true -Djdk.http.auth.tunneling.disabledSchemes="" -XX:+HeapDumpOnOutOfMemoryError -XX:-OmitStackTraceInFastThrow -Xverify:none
پس از این تغییرات اگر Rider را اجرا کنید، حداقل نسبت به قبل دو برابر RAM مصرف خواهد کرد. همچنین بار اولی که برنامه را اجرا میکنید، چون تعدادی از این تنظیمات بر روی نحوهی JIT تاثیرگذار هستند، کمی طول میکشد تا کار کامپایل جدید آن صورت گیرد و از دفعات آتی اجرای آن، بهبود کارآیی را احساس خواهید کرد.
علاوه بر موارد فوق، فایل C:\Program Files\JetBrains\JetBrains Rider 2018.3.4\bin\idea.properties را نیز میتوانید جهت اعمال تغییرات زیر ویرایش کنید:
idea.max.intellisense.filesize=3500 idea.cycle.buffer.size=2048
<?xml version="1.0" encoding="utf-8"?> <packages> <package id="Microsoft.AspNet.OData" version="6.0.0" targetFramework="net462" /> <package id="Microsoft.AspNet.SignalR.Core" version="2.2.1" targetFramework="net462" /> <package id="Microsoft.AspNet.SignalR.Owin" version="1.2.2" targetFramework="net462" /> <package id="Microsoft.AspNet.WebApi.Client" version="5.2.3" targetFramework="net462" /> <package id="Microsoft.AspNet.WebApi.Core" version="5.2.3" targetFramework="net462" /> <package id="Microsoft.AspNet.WebApi.Owin" version="5.2.3" targetFramework="net462" /> <package id="Microsoft.CodeDom.Providers.DotNetCompilerPlatform" version="1.0.2" targetFramework="net462" /> <package id="Microsoft.Extensions.DependencyInjection" version="1.0.0" targetFramework="net462" /> <package id="Microsoft.Extensions.DependencyInjection.Abstractions" version="1.0.0" targetFramework="net462" /> <package id="Microsoft.Net.Compilers" version="1.3.2" targetFramework="net462" developmentDependency="true" /> <package id="Microsoft.OData.Core" version="7.0.0" targetFramework="net462" /> <package id="Microsoft.OData.Edm" version="7.0.0" targetFramework="net462" /> <package id="Microsoft.Owin" version="3.0.1" targetFramework="net462" /> <package id="Microsoft.Owin.FileSystems" version="3.0.1" targetFramework="net462" /> <package id="Microsoft.Owin.Host.SystemWeb" version="3.0.1" targetFramework="net462" /> <package id="Microsoft.Owin.Security" version="3.0.1" targetFramework="net462" /> <package id="Microsoft.Owin.StaticFiles" version="3.0.1" targetFramework="net462" /> <package id="Microsoft.Spatial" version="7.0.0" targetFramework="net462" /> <package id="Newtonsoft.Json" version="9.0.1" targetFramework="net462" /> <package id="Owin" version="1.0" targetFramework="net462" /> <package id="System.Collections" version="4.0.11" targetFramework="net462" /> <package id="System.Collections.Concurrent" version="4.0.12" targetFramework="net462" /> <package id="System.ComponentModel" version="4.0.1" targetFramework="net462" /> <package id="System.Diagnostics.Debug" version="4.0.11" targetFramework="net462" /> <package id="System.Globalization" version="4.0.11" targetFramework="net462" /> <package id="System.Linq" version="4.1.0" targetFramework="net462" /> <package id="System.Linq.Expressions" version="4.1.0" targetFramework="net462" /> <package id="System.Reflection" version="4.1.0" targetFramework="net462" /> <package id="System.Resources.ResourceManager" version="4.0.1" targetFramework="net462" /> <package id="System.Runtime.Extensions" version="4.1.0" targetFramework="net462" /> <package id="System.Threading" version="4.0.11" targetFramework="net462" /> <package id="System.Threading.Tasks" version="4.0.11" targetFramework="net462" /> </packages>
PM>Update-Package -reinstall -Project YourProjectName
<?xml version="1.0" encoding="utf-8"?> <configuration> <appSettings> <add key="owin:AppStartup" value="OwinKatanaTest.OwinAppStartup, OwinKatanaTest" /> <!-- Owin App Startup Class --> <add key="webpages:Enabled" value="false" /> <!-- Disable asp.net web pages. Note that based on our current configuration, asp.net web forms, mvc and web pages won't work. This configuration is for owin stuffs only, for example asp.net web api & odata, signalr, etc. --> </appSettings> <system.web> <compilation debug="true" defaultLanguage="c#" enablePrefetchOptimization="true" optimizeCompilations="true" targetFramework="4.6.2"> <assemblies> <remove assembly="*" /> <!-- To improve app startup performance, our app will continue its work without this compilations, these are required for asp.net web forms, mvc and web pages. --> <add assembly="OwinKatanaTest" /> </assemblies> </compilation> <httpRuntime targetFramework="4.6.2" /> <httpModules> <!-- No need to these modules and handlers, owin handler itself will do everything for us --> <clear /> </httpModules> <httpHandlers> <clear /> </httpHandlers> <sessionState mode="Off" /> </system.web> <system.codedom> <compilers> <compiler language="c#;cs;csharp" extension=".cs" type="Microsoft.CodeDom.Providers.DotNetCompilerPlatform.CSharpCodeProvider, Microsoft.CodeDom.Providers.DotNetCompilerPlatform, Version=1.0.2.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" warningLevel="4" compilerOptions="/langversion:6 /nowarn:1659;1699;1701" /> </compilers> </system.codedom> <runtime> <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1"> <dependentAssembly> <assemblyIdentity name="Microsoft.Owin" publicKeyToken="31bf3856ad364e35" culture="neutral" /> <bindingRedirect oldVersion="0.0.0.0-3.0.1.0" newVersion="3.0.1.0" /> </dependentAssembly> <dependentAssembly> <assemblyIdentity name="Newtonsoft.Json" publicKeyToken="30ad4fe6b2a6aeed" culture="neutral" /> <bindingRedirect oldVersion="0.0.0.0-9.0.0.0" newVersion="9.0.0.0" /> </dependentAssembly> <dependentAssembly> <assemblyIdentity name="Microsoft.Owin.Security" publicKeyToken="31bf3856ad364e35" culture="neutral" /> <bindingRedirect oldVersion="0.0.0.0-3.0.1.0" newVersion="3.0.1.0" /> </dependentAssembly> <dependentAssembly> <assemblyIdentity name="Microsoft.AspNet.SignalR.Core" publicKeyToken="31bf3856ad364e35" culture="neutral" /> <bindingRedirect oldVersion="0.0.0.0-2.2.1.0" newVersion="2.2.1.0" /> </dependentAssembly> <dependentAssembly> <assemblyIdentity name="System.Reflection" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" /> <bindingRedirect oldVersion="0.0.0.0-4.1.0.0" newVersion="4.1.0.0" /> </dependentAssembly> <dependentAssembly> <assemblyIdentity name="System.Runtime.Extensions" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" /> <bindingRedirect oldVersion="0.0.0.0-4.1.0.0" newVersion="4.1.0.0" /> </dependentAssembly> <dependentAssembly> <assemblyIdentity name="System.Web.Http" publicKeyToken="31bf3856ad364e35" culture="neutral" /> <bindingRedirect oldVersion="0.0.0.0-5.2.3.0" newVersion="5.2.3.0" /> </dependentAssembly> <dependentAssembly> <assemblyIdentity name="System.Net.Http.Formatting" publicKeyToken="31bf3856ad364e35" culture="neutral" /> <bindingRedirect oldVersion="0.0.0.0-5.2.3.0" newVersion="5.2.3.0" /> </dependentAssembly> </assemblyBinding> </runtime> <system.webServer> <validation validateIntegratedModeConfiguration="false" /> <modules runAllManagedModulesForAllRequests="false"> <!-- We're not going to remove all modules, some modules such as static & dynamic compression modules are really cool (-: --> <remove name="RewriteModule" /> <remove name="OutputCache" /> <remove name="Session" /> <remove name="WindowsAuthentication" /> <remove name="FormsAuthentication" /> <remove name="DefaultAuthentication" /> <remove name="RoleManager" /> <remove name="FileAuthorization" /> <remove name="UrlAuthorization" /> <remove name="AnonymousIdentification" /> <remove name="Profile" /> <remove name="UrlMappingsModule" /> <remove name="ServiceModel-4.0" /> <remove name="UrlRoutingModule-4.0" /> <remove name="ScriptModule-4.0" /> <remove name="Isapi" /> <remove name="IsapiFilter" /> <remove name="DigestAuthentication" /> <remove name="WindowsAuthentication" /> <remove name="ServerSideInclude" /> <remove name="DirectoryListing" /> <remove name="DefaultDocument" /> <remove name="CustomError" /> <remove name="Cgi" /> </modules> <defaultDocument> <!-- Default docs will be configured using owin static files middleware --> <files> <clear /> </files> </defaultDocument> <handlers> <!-- Only use this handler for all requests --> <clear /> <add name="Owin" verb="*" path="*" type="Microsoft.Owin.Host.SystemWeb.OwinHttpHandler, Microsoft.Owin.Host.SystemWeb" /> </handlers> <httpProtocol> <customHeaders> <clear /> </customHeaders> </httpProtocol> </system.webServer> </configuration>
بعد از build کردن پروژه، در صورت خطا داشتن از Referencesها، System.Reflection و System.Runtime.Extensions را حذف کنید.
using Microsoft.AspNet.SignalR; using Microsoft.OData; using Microsoft.OData.Edm; using Owin; using OwinKatanaTest.Model; using OwinKatanaTest.ODataControllers; using System.Collections.Generic; using System.Web.Http; using System.Web.OData.Builder; using System.Web.OData.Extensions; using System.Web.OData.Routing.Conventions; namespace OwinKatanaTest { public class OwinAppStartup { public void Configuration(IAppBuilder owinApp) { owinApp.Map("/odata", innerOwinAppForOData => { HttpConfiguration webApiODataConfig = new HttpConfiguration(); webApiODataConfig.IncludeErrorDetailPolicy = IncludeErrorDetailPolicy.Always; webApiODataConfig.Formatters.Clear(); IEnumerable<IODataRoutingConvention> conventions = ODataRoutingConventions.CreateDefault(); ODataModelBuilder modelBuilder = new ODataConventionModelBuilder(webApiODataConfig); modelBuilder.Namespace = modelBuilder.ContainerName = "Test"; var categoriesSetConfig = modelBuilder.EntitySet<Category>("Categories"); var getBestCategoryFunctionConfig = categoriesSetConfig.EntityType.Collection.Function(nameof(CategoriesController.GetBestCategory)); getBestCategoryFunctionConfig.ReturnsFromEntitySet<Category>("Categories"); IEdmModel edmModel = modelBuilder.GetEdmModel(); webApiODataConfig.MapODataServiceRoute("default", "", builder => { builder.AddService(ServiceLifetime.Singleton, sp => conventions); builder.AddService(ServiceLifetime.Singleton, sp => edmModel); }); innerOwinAppForOData.UseWebApi(webApiODataConfig); }); owinApp.Map("/api", innerOwinAppForWebApi => { HttpConfiguration webApiConfig = new HttpConfiguration(); webApiConfig.IncludeErrorDetailPolicy = IncludeErrorDetailPolicy.Always; webApiConfig.MapHttpAttributeRoutes(); webApiConfig.Routes.MapHttpRoute(name: "default", routeTemplate: "{controller}/{action}", defaults: new { action = RouteParameter.Optional }); innerOwinAppForWebApi.UseWebApi(webApiConfig); }); owinApp.Map("/signalr", innerOwinAppForSignalR => { innerOwinAppForSignalR.RunSignalR(new HubConfiguration { EnableDetailedErrors = true }); }); owinApp.UseStaticFiles(); owinApp.Run(async context => { await context.Response.WriteAsync("owin katana"); }); } } }
using OwinKatanaTest.Model; using System.Web.Http; using System.Web.OData; namespace OwinKatanaTest.ODataControllers { public class CategoriesController : ODataController { [HttpGet] public Category GetBestCategory() { return new Category { Id = 1, Name = "Test" }; } } }
using OwinKatanaTest.Model; using System.Collections.Generic; using System.Web.Http; namespace OwinKatanaTest.ApiControllers { public class ProductsController : ApiController { [HttpGet] [Route("products/{categoryId}")] public List<Product> GetProductsByCategoryId(int categoryId) { return new List<Product> { new Product { Id = 1 , Name = "Test" } }; } } }
<package id="Microsoft.Owin.Testing" version="3.0.1" targetFramework="net462" />
در ادامه تست خود را اینگونه مینویسیم
using Microsoft.Owin.Testing; using Microsoft.VisualStudio.TestTools.UnitTesting; using OwinKatanaTest; using System.Net; using System.Net.Http; using System.Threading.Tasks; namespace Test { [TestClass] public class Test { [TestMethod] public async Task TestWebApi() { using (TestServer server = TestServer.Create<OwinAppStartup>()) { HttpResponseMessage apiResponse = await server.HttpClient.GetAsync("/api/products/1"); apiResponse.EnsureSuccessStatusCode(); Assert.AreEqual(HttpStatusCode.OK, apiResponse.StatusCode); } } [TestMethod] public async Task TestOData() { using (TestServer server = TestServer.Create<OwinAppStartup>()) { HttpResponseMessage odataResponse = await server.HttpClient.GetAsync("odata/Categories/Test.GetBestCategory"); odataResponse.EnsureSuccessStatusCode(); Assert.AreEqual(HttpStatusCode.OK, odataResponse.StatusCode); } } } }