HttpContext در BlazorSSR
سلام
مشابه سایت جاری از یک CascadingValue بنام ApplicationState در برنامه استفاده کرده ام. در این ApplicationState اعضایی وجود دارند مانند
public CurrentUserModel? CurrentUser { set; get; } public string? UserName { get; set; } [CascadingParameter] public HttpContext HttpContext { set; get; }
چنانچه فایل App برنامه بصورت زیر باشد.
<head> <HeadOutlet /> </head> <body> <Routes /> </body> </html>
HttpContext نال نیست ولی اعضای دیگر مانند CurrentUser , UserName در سایر صفحات نال نشان میدهد. نمونه عکس زیر
پس از تغییر HeadOutlet و Routes فایل App به InteractiveServer اینبار برعکس میشود و HttpContext نال میشود و اعضای دیگر ApplicationState مقدار دارند
<body> <Routes @rendermode="RenderModeForPage" /> <script src="_framework/blazor.web.js"></script> </body> </html> @code { [CascadingParameter] private HttpContext HttpContext { get; set; } = default!; private IComponentRenderMode? RenderModeForPage => HttpContext.Request.Path.StartsWithSegments("/Account") ? null : InteractiveServer; }
مشابه عکس زیر
راه حل نال نشدن همه اعضاء در هردو صورت InteractiveServer و یا غیر InteractiveServer چیست؟
با تزریق
[Inject] IHttpContextAccessor HttpContextAccessor{ get; set; }
در ApplicationState و مقدار دهی HttpContext مشکل حل میشود ولی شما در سایت از این روش استفاده نکرده اید
protected override async Task OnInitializedAsync() { this.HttpContext = HttpContextAccessor.HttpContext; CurrentUser = new CurrentUserModel { IsAdmin = true }; }
<%@ Page Language="C#" ValidateRequest="false" %>
در ASP.NET 4.5 مفهوم جدیدی به نام Deferred validation معرفی شدهاست تا رفتار Web forms را نیز در برابر ورودیهای ارسال شده همانند ASP.NET MVC کند. به این معنا که اگر جهت دسترسی به مقدار کوئری استرینگ lastName همانند قبل عمل کنید:
Request["lastName"]
اما اگر در ASP.NET 4.5، کوئری استرینگ را به نحو ذیل دریافت کنید:
Request.Unvalidated.QueryString["lastName"]
برای دسترسی به مقادیر اعتبارسنجی نشده فیلدهای یک فرم ارسالی نیز میتوان به صورت ذیل عمل کرد:
Request.Unvalidated.Form[txtData1.UniqueID]
در اینجا اگر دقت کرده باشید از UniqueID بجای Id استفاده شدهاست؛ از این جهت که مجموعه Form، حاوی Idهای واقعی دریافت شده از کاربر مانند ctl00$MainContent$MessageText میباشد.
روش دوم، استفاده از خاصیت جدید ValidateRequestMode یک کنترل سمت سرور است و در اینجا نیز میتوان صرفا اعتبارسنجی یک کنترل را بجای کل صفحه، غیرفعال کرد:
<asp:TextBox ID="txtASPNet" ValidateRequestMode="Disabled" runat="server" />
البته برای فعال سازی اعتبارسنجی با تاخیر نیاز است اصلاح ذیل را نیز به web.config برنامه اعمال کنید (مقدار پیش فرض آن 4 است):
<httpRuntime targetFramework="4.5" requestValidationMode="4.5" />
- یک ASP.NET Web Application با نام NorthwindService ایجاد نمایید و بر روی پروژه راست کلیک کنید و از منوی Add گزینه New Item را انتخاب نمایید از پنجره باز شده از دسته Data گزینه ADO.NET Entity Data Model را انتخاب و نام ان را Northwind بگذارید.
- از پنجره باز شده Generate from Databaseرا انتخاب و با انتخاب کانکشن از نوع Sql Server Compact 4 اتصال به فایل Northwind.sdf را انتخاب تا کلاسهای لازم تولید شود.
- برای تولید data service بر روی پروژه راست کلیک کنید و از منوی Add گزینه New Item را انتخاب نمایید از پنجره باز شده گزینه
WCF Data Service را انتخاب و نام آن را Northwind.svc بگذارید. کد زیر خودکار تولید میشود
public class Northwind : DataService< /* TODO: put your data source class name here */ > { // This method is called only once to initialize service-wide policies. public static void InitializeService(DataServiceConfiguration config) { // TODO: set rules to indicate which entity sets and service operations are visible, updatable, etc. // Examples: // config.SetEntitySetAccessRule("MyEntityset", EntitySetRights.AllRead); // config.SetServiceOperationAccessRule("MyServiceOperation", ServiceOperationRights.All); config.DataServiceBehavior.MaxProtocolVersion = DataServiceProtocolVersion.V3; } }
- برای دسترسی به موجودیتهای Northwind بجای عبارت put your data source نام مدل را تایپ کنید
public class Northwind : DataService<NorthwindEntities>
- برای فعال کردن دسترسی به منابع data source متغیر config کلاس DataServiceConfiguration را بصورت زیر تنظیم نمایید. تابع SetEntitySetAccessRule با گرفتن نام موجودیت و نحوه دسترسی امکان استفاده از این موجودیت را با استفاده از WCF Data Service فزاهم مینمایید. مثلا در زیر امکان دسترسی به موجودیت Orders را با امکان خواندن همه، نوشتن ادقامی و جایگزین فراهم نموده است.
config.SetEntitySetAccessRule("Orders", EntitySetRights.AllRead | EntitySetRights.WriteMerge | EntitySetRights.WriteReplace ); config.SetEntitySetAccessRule("Customers", EntitySetRights.AllRead);
- اگر بخواهیم امکان خواندن همه موجودیتها را فراهم کنیم از کد زیر میتوانیم استفاده نمایید که * به معنای همه موجودیتهای data model میباشد
config.SetEntitySetAccessRule("*", EntitySetRights.AllRead);
<service xmlns="http://www.w3.org/2007/app" xmlns:atom="http://www.w3.org/2005/Atom" xml:base="http://localhost:8358/Northwind.svc/"> <workspace> <atom:title>Default</atom:title> <collection href="Categories"> <atom:title>Categories</atom:title> </collection> <collection href="Customers"> <atom:title>Customers</atom:title> </collection> <collection href="Employees"> <atom:title>Employees</atom:title> </collection> <collection href="Order_Details"> <atom:title>Order_Details</atom:title> </collection> <collection href="Orders"> <atom:title>Orders</atom:title> </collection> <collection href="Products"> <atom:title>Products</atom:title> </collection> <collection href="Shippers"> <atom:title>Shippers</atom:title> </collection> <collection href="Suppliers"> <atom:title>Suppliers</atom:title> </collection> </workspace> </service>
- بر روی Solution پروژه جاری راست کلیک و از منوی Add گزینه New Project را انتخاب و یک پروژه از نوع WPF Application با نام NorthwindClient ایجاد نمایید.
- در پنجره MainWindow مانند کد زیر از یک Combobox و DataGrid برای نمایش اطلاعات استفاده نمایید
<Window x:Class="MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="Northwind Orders" Height="335" Width="425" Name="OrdersWindow" Loaded="Window1_Loaded"> <Grid Name="orderItemsGrid"> <ComboBox DisplayMemberPath="Order_ID" ItemsSource="{Binding}" IsSynchronizedWithCurrentItem="true" Height="23" Margin="92,12,198,0" Name="comboBoxOrder" VerticalAlignment="Top"/> <DataGrid ItemsSource="{Binding Path=Order_Details}" CanUserAddRows="False" CanUserDeleteRows="False" Name="orderItemsDataGrid" Margin="34,46,34,50" AutoGenerateColumns="False"> <DataGrid.Columns> <DataGridTextColumn Header="Product" Binding="{Binding Product_ID, Mode=OneWay}" /> <DataGridTextColumn Header="Quantity" Binding="{Binding Quantity, Mode=TwoWay}" /> <DataGridTextColumn Header="Price" Binding="{Binding UnitPrice, Mode=TwoWay}" /> <DataGridTextColumn Header="Discount" Binding="{Binding Discount, Mode=TwoWay}" /> </DataGrid.Columns> </DataGrid> <Label Height="28" Margin="34,12,0,0" Name="orderLabel" VerticalAlignment="Top" HorizontalAlignment="Left" Width="65">Order:</Label> <StackPanel Name="Buttons" Orientation="Horizontal" HorizontalAlignment="Right" Height="40" Margin="0,257,22,0"> <Button Height="23" HorizontalAlignment="Right" Margin="0,0,12,12" Name="buttonSave" VerticalAlignment="Bottom" Width="75" Click="buttonSaveChanges_Click">Save Changes </Button> <Button Height="23" Margin="0,0,12,12" Name="buttonClose" VerticalAlignment="Bottom" Width="75" Click="buttonClose_Click">Close</Button> </StackPanel> </Grid> </Window>
- برای ارجاع به wcf data service بر روی پروژه راست کلیک و گزینه Add Service Reference را انتخاب نمایید در پنجره باز شده گزینه Discover را انتخاب تا سرویس را یافته و نام Namespase را Northwind بگذارید.
- حال مانند کد زیر یک شی از مدل NorthwindEntities با آدرس وب سرویس ایجاد نموده ایم و نتیحه کوئری با استفاده از کلاس DataServiceCollection به DataContext گرید انتصاب داده ایم که البته پیش فرض آن آشنایی با DataBinding در WPF است.
private NorthwindEntities context; private string customerId = "ALFKI"; private Uri svcUri = new Uri("http://localhost:8358/Northwind.svc"); private void Window1_Loaded(object sender, RoutedEventArgs e) { try { context = new NorthwindEntities(svcUri); var ordersQuery = from o in context.Orders.Expand("Order_Details") where o.Customers.Customer_ID == customerId select o; DataServiceCollection<Orders> customerOrders = new DataServiceCollection<Orders>(ordersQuery); this.orderItemsGrid.DataContext = customerOrders; } catch (Exception ex) { MessageBox.Show(ex.ToString()); } }
- با صدا زدن تابع SaveChanges مدل میتوانید تغییرات را در پایگاه داده ذخیره نمایید.
private void buttonSaveChanges_Click(object sender, RoutedEventArgs e) { try { context.SaveChanges(); } catch (DataServiceRequestException ex) { MessageBox.Show(ex.ToString()); } }
- برنامه را اجرا نمایید تا خروجی کار را مشاهده نمایید. مقادیر Quantity را تغییر دهید و دکمه Save Changes را انتخاب تا تغییرات دخیره شود.
ASP.NET MVC #3
تهیه پیشنیازهای شروع به کار با ASP.NET MVC
در زمان نگارش این مطلب، نگارش نهایی ASP.NET MVC 3 در دسترس است و همچنین نگارش بتای 4 آن نیز قابل دریافت و نصب میباشد. بنابراین فعلا اساس را بر مبنای نگارشی قرار خواهیم داد که در محیط کاری قابل استفاده باشد.
ASP.NET MVC 3 پس از ارائه Visual Studio 2010، منتشر شد و VS.NET به صورت پیش فرض به همراه ASP.NET MVC 2 است. سادهترین روش نصب ASP.NET MVC 3 بر روی VS 2010 استفاده از برنامه رایگانی است به نام Web Platform Installer. این برنامه را از این آدرس میتوان دریافت کرد: http://microsoft.com/web/downloads
پس از دریافت آن حداقل دو راه برای نصب ASP.NET MVC 3 وجود دارد. یا گزینهی نصب ASP.NET MVC 3 Tools Update را انتخاب کنید و یا سرویس پک یک VS 2010 را از طریق این برنامه یا جداگانه (بسته کامل و مستقل) دریافت و نصب نمائید. VS 2010 SP1 نیز به همراه ASP.NET MVC 3 است؛ همچنین IIS Express را که نسخه ساده شده IIS 7.5 مخصوص توسعه دهندهها است، میتوان با این نگارش یکپارچه کرد.
بنابراین به صورت خلاصه بهترین کار این است که سرویس پک یک VS 2010 را یکبار نصب نمائید. اگر این نصب از طریق برنامه Web Platform Installer باشد، به صورت خودکار IIS Express را هم انتخاب و نصب خواهد کرد. اگر فقط SP1 را به صورت مستقل دریافت کردهاید، حاوی IIS Express نیست و باید جداگانه آنرا دریافت و نصب نمائید (^). البته نصب IIS Express در اینجا یک گزینه اختیاری است و الزامی نیست.
مروری بر ساختار یک پروژه ASP.NET MVC
پس از نصب پیش نیازها، امکان انتخاب یک پروژه وب ASP.NET MVC 3 در VS 2010 میسر خواهد شد:
در اینجا گزینهی ASP.NET MVC 3 Web Application را انتخاب میکنیم. در صفحه بعدی که ظاهر میشود:
حالت Internet Application به همراه یک سری مدل و کنترلر از پیش نوشته شده جهت مدیریت ورود به سایت و ثبت نام در سایت است و حالت Empty تنها به همراه ساختار پیش فرض پوشههای یک پروژه ASP.NET MVC است.
فعلا جهت توضیحات اولیه بیشتر، گزینهی Internet Application و نوع View Engine را هم ASPX انتخاب میکنیم. کار View Engine، رندر یک View به شکل HTML و ارائه نهایی اطلاعات آن به کاربر است. این نوعهای متفاوت هم فقط در Syntax تفاوت دارند (به آن templating language هم گفته میشود). نوع ASPX همان Syntax متداول قدیمی ASP.NET را تداعی میکند و نوع Razor به صورت اختصاصی برای ASP.NET MVC تهیه شده است.
باید در نظر داشت که گزینه مرجح از نگارش 3 به بعد، Razor است (البته این هم سلیقهای است. اگر هیچکدام از این دو را هم نخواهید استفاده کنید مشکلی نیست! میشود کلا آن را عوض کرد). هدفم هم از انتخاب ASPX نمایش یک سری ریزه کاری است که شاید برای برنامه نویسهای ASP.NET Web forms جالب باشد. این موارد را در حالت انتخاب Razor به این وضوح مشاهده نخواهید کرد و محیط خیلی ساده شده است.
همانطور که ملاحظه میکنید این فریم ورک یک سری پوشه پیش فرض را توصیه میکند. بدیهی است که ضرورتی ندارد تا پوشه Models یا پوشه Controllers حتما در همین پروژه قرار داشته باشند؛ چون زمانیکه پروژه کامپایل شد، محل این پوشه بندیها آنچنان اهمیتی ندارد.
نکته جالب در این تصویر، فایل Site.Master است. بله، این فایل شبیه به همان فایل master page موجود در ASP.NET Web form است که قالب کلی سایت را به همراه داشته و سایر صفحات، قالب خود را از آن به ارث میبرند. حتی تگ runat=server هم به وضوح در این فایل، در چندین جای آن قابل مشاهده است. تنها تفاوت آن نداشتن فایل code behind است. asp:ContentPlaceHolder نیز در آن تعریف شده است. خلاصه این محیط جدید به معنای دور ریختن تمام آنچیزی که در Web forms وجود دارد نیست. برای نمونه اگر فایل ChangePassword.aspx موجود در پوشه Account را باز کنید، باز هم همان asp:Content معروف به همراه تگ runat=server قابل مشاهده است. برای مثال این محتوای صفحه Error.aspx پیش فرض آن است:
<%@ Page Language="C#" MasterPageFile="~/Views/Shared/Site.Master"
Inherits="System.Web.Mvc.ViewPage<System.Web.Mvc.HandleErrorInfo>" %>
<asp:Content ID="errorTitle" ContentPlaceHolderID="TitleContent" runat="server">
Error
</asp:Content>
<asp:Content ID="errorContent" ContentPlaceHolderID="MainContent" runat="server">
<h2>
Sorry, an error occurred while processing your request.
</h2>
</asp:Content>
اگر از قسمت Inherits آن صرفنظر کنیم، «هیچ» تفاوتی با ASP.NET Web forms ندارد؛ علت هم به این بر میگردد که موتوری که Web forms و MVC از آن استفاده میکنند، یکی است. هر دو بر فراز موتور ASP.NET معنا پیدا خواهند کرد.
قرار دادهای پوشههای پیش فرض یک پروژه ASP.NET MVC
- پوشه Controllers حاوی کلاسهای کنترلری است که درخواستهای رسیده را مدیریت میکنند.
- پوشه Models حاوی کلاسهایی است که اشیاء تجاری و همچنین کار با اطلاعات را تعریف و مدیریت میکنند.
- در پوشه Views، فایلهای قالبهای رابط کاربری که مسئول ارائه خروجی به کاربر هستند قرار میگیرند. همچنین مطابق قرارداد دیگری، اگر نام کنترلر ما مثلا ProductController باشد (با توجه به اینکه نام کلاس آن هم مطابق قرارداد، مختوم به کلمه Controller است)، فایلهای Viewهای مرتبط با آن در پوشه Views/Product قرار خواهند گرفت.
- در پوشه Scripts، فایلهای جاوا اسکریپت مورد استفاده در سایت قرار خواهند گرفت.
- پوشه Content محل قرارگیری فایلهای CSS و تصاویر است.
- پوشه App_Data جایی است که فایلهایی با قابلیت read/write در آن قرار میگیرند (و باید دقت داشت که فقط همینجا هم باید قرار گیرند و گرنه این نوشتنها در مکانهای متفرقه، ممکن است سبب ری استارت شدن برنامه شوند:(^)).
چندی پیش در سایت جاری چند مقاله خوب توسط یکی از دوستان درباره Qunit منتشر شد. Qunit یک ابزار قدرتمند و مناسب برای تست کدهای جاوااسکریپت است و در اثبات صحت این گفته همین کافیست که بدانیم برای تست کدهای نوشته شده در پروژههای متن بازی هم چون Backbone.Js و JQuery از این فریم ورک استفاده شده است. اما به احتمال قوی در ذهن شما این سوال مطرح شده است که خب! در صورت آشنایی با Qunit چه نیاز به یادگیری Jasmine یا خدای نکرده Mocha و FuncUnit است؟ هدف صرفا معرفی یک ابزار غیر برای تست کد است نه مقایسه و نتیجه گیری برای تعیین میزان برتری این ابزارها. اصولا مهمترین دلیل برای انتخاب، علاوه بر امکانات و انعطاف پذیری، فاکتور راحتی و آسان بودن در هنگام استفاده است که به صورت مستقیم به شما و تیم توسعه نرم افزار بستگی دارد.
اما به عنوان توسعه دهنده نرم افزار که قرار است از این ابزار استفاده کنیم بهتر است با تفاوتها و شباهتهای مهم این دو فریم ورک آشنا باشیم:
»Jasmine یک فریم ورک تست کدهای جاوا اسکریپ بر مبنای Behavior-Driven Development است در حالی که Qunit بر مبنای Test-Driven Development است و همین مسئله مهمترین تفاوت بین این دو فریم ورک میباشد.
»اگر قصد دارید که از Qunit نیز به روش BDD استفاده نمایید باید از ترکیب Pavlov به همراه Qunit استفاده کنید.
»Jasmine از مباحث مربوط به Spies و Mocking به خوبی پشتیبانی میکند ولی این امکان به صورت توکار در Qunit فراهم نیست. برای اینکه بتوانیم این مفاهیم را در Qunit پیاده سازی کنیم باید از فریم ورکهای دیگر نظیر SinonJS به همراه Qunit استفاده کنیم.
»هر دو فریم ورک بالا به سادگی و راحتی کار معروف هستند
»تمام موارد مربوط به الگوهای Matching در هر دو فریم ورک به خوبی تعبیه شده است
» هر دو فریم ورک بالا از مباحث مربوط به Asynchronous Testing برای تست کدهای Ajax ای به خوبی پشتیبانی میکنند.
بررسی چند مفهوم
قبل از شروع، بهتر است که با چند مفهوم کلی و در عین حال مهم این فریم ورک آشنا شویم
describe('JavaScript addition operator', function () { it('adds two numbers together', function () { expect(1 + 2).toEqual(3); }); });
در تابع it کد بالا شما میتوانید کدهای مربوط بدنه توابع تست خود را بنویسید. برای پیاده سازی Assert در توابع تست مفهوم expectationها وجود دارد. در واقع expect برای بررسی مقادیر حقیقی با مقادیر مورد انتظار مورد استفاده قرار میگیرد و شامل مقادیر true یا false خواهد بود.
برای Setup و Teardown توابع تست خود باید از توابع beforeEach و afterEach که بدین منظور تعبیه شده اند استفاده کنید.
describe("A spec (with setup and tear-down)", function() { var foo; beforeEach(function() { foo = 0; foo += 1; }); afterEach(function() { foo = 0; }); it("is just a function, so it can contain any code", function() { expect(foo).toEqual(1); }); it("can have more than one expectation", function() { expect(foo).toEqual(1); expect(true).toEqual(true); }); });
اگر در کد تست خود قصد دارید که یک تابع describe یا it را غیر فعال کنید کافیست یک x به ابتدای آنها اضافه کنید و دیگر نیاز به هیچ کار اضافه دیگری برای comment کردن کد نیست.
xdescribe("A spec", function() { var foo; beforeEach(function() { foo = 0; foo += 1; }); xit("is just a function, so it can contain any code", function() { expect(foo).toEqual(1); }); });
درادامه قصد پیاده سازی یک مثال را با استفاده از Jasmine و RequireJs در پروژه Asp.Net MVC دارم.
- فولدر lib شامل فایلها کدهای Jasmine برای setup و tear down و spice و تست کدهای شما میباشد.
- فایل specRunner.html به واقع یک فایل برای نمایش فایلهای تست و همچنین نمایش نتیجه تست است.
- فولدر spec نیز شامل کدهای Jasmine برای کمک به نوشتن تست میباشد.
در این مثال قصد داریم فایلهای player.js و song.js که به عنوان نمونه به همراه این فریم ورک قرار دارد را در قالب یک پروژه MVC به همراه RequireJs، تست نماییم. در نتیجه این فایلها را از فولدر src انتخاب نمایید و آنها را در قسمت Scripts پروژه اصلی خود کپی کنید(ابتدا بک پوشه به نام App بسازید و فایلها را در آن قرار دهید)
برای استفاده از requireJs باید دستور define را در ابتدا این فایلها اضافه نماییم. در نتیجه فایلهای Player.js و Song.js را باز کنید و تغییرات زیر را در ابتدای این فایلها اعمال نمایید.
Song.js
define(function () { function Song() { } Song.prototype.persistFavoriteStatus = function (value) { // something complicated throw new Error("not yet implemented"); }; });
define(function () { function Player() { } Player.prototype.play = function (song) { this.currentlyPlayingSong = song; this.isPlaying = true; }; Player.prototype.pause = function () { this.isPlaying = false; }; Player.prototype.resume = function () { if (this.isPlaying) { throw new Error("song is already playing"); } this.isPlaying = true; }; Player.prototype.makeFavorite = function () { this.currentlyPlayingSong.persistFavoriteStatus(true); }; });
baseUrl در پیکر بندی requireJs به مسیر فایلهای پروژه که در پروژه اصلی MVC قرار دارد اشاره میکند. paths برای تعیین مسیر فایلهای تست که در پوشه spec در پروژه تست قرار دارد اشاره میکند. اگر دقت کرده باشید به دلیل اینگه تگهای script مربوط به لود فایلهای SpecHelper.js و PlayerSpec.js به صورت comment در آمده اند در نتیجه این فایلها لود نخواهند شد و خروجی مورد نظر مشاهده نمیشود. در این جا باید از مکانیزم AMD موجود در RequireJs استفاده نماییم و فایلهای مربوطه را لود کنیم. برای این کار نیاز به اضافه کردن دستور require در ابتدای تگ script به صورت زیر در این فایل است. در نتیجه فایلهای PlayerSpec و SpecHelper نیز توسط RequireJs لود خواهند شد.
نیاز به یک تغییر کوچک دیگر نیز وجود دارد. فایل PlayerSpec را باز نمایید و وابستگی فایلهای آن را تعیین نمایید. از آن جا که این فایل برای تست فایلهای Player , Song ایجاد شده است در نتیجه باید از define برای تعیین این وابستگیها استفاده نماییم.
یادآوری:
»دستور describe در فایل بالا برای تعریف تابع تست است. همان طور که میبینید بک نام به آن داده میشود به همراه بدنه تابع تست.
»دستور beforeEach برای آماده سازی مواردی است که قصد داریم در تست مورد استفاده قرار گیرند. همانند متدهای Setup در UnitTest.
» دستور expect نیز معادل Assert در UnitTest است و برای بررسی صحت عملکرد تست نوشته میشود.
اگر
فایل SpecRunner.html را دوباره در مرورگر خود باز نمایید تصویر زیر را
مشاهده خواهید کرد که به عنوان موفقیت آمیز بودن پیکر بندی پروژه و تستهای آن میباشد.
استفاده از Kendo UI templates
قالب پویا بر اساس اطلاعات یک ردیف
#= productDetails(data) #
<script> function productDetails(product) { var action = '@Url.Action("ProductDetails", "Product")'; var html = kendo.format("<a href='{0}/{1}'>Show Product Details</a>", action, product.ProductID ); return html; } </script>