یکی از مشکلات سینتکس Razor سمت سرور، این است که در فایلهای JavaScript و CSS سمت کاربر نمیتوانیم از آن استفاده کنیم، به عنوان مثال فرض کنید در یک فایل JavaScript نیاز به مشخص سازی آدرس یک اکشن متد دارید؛ مثلاً انجام یک عملیات ایجکسی. در این حالت به عنوان یک Best Practice بهتر است از Url.Action استفاده کنید. اما همانطور که عنوان شد، این امکان یعنی استفاده از سینتکس Razor در فایلهای JS و CSS مهیا نیست.
سادهترین راهحل، تولید ویوهای سمت سرور JavaScript ایی است. برای اینکار تنها کاری که باید انجام دهیم، تغییر مقدار Content-Type صفحه به مقدار موردنظر میباشد؛ مثلاً text/javascript برای فایلهای JS و text/css برای فایلهای CSS. به عنوان مثال برای فایلهای CSS به این صورت عمل خواهیم کرد:
public ActionResult Style() { Response.ContentType = "text/css"; var model = new Style { Color = "red", Background = "blue" }; return View(model); }
@model ExternalJavaScript.Models.Style @{ Layout = null; } body { color : @Model.Color; background-color : @Model.Background; }
<link rel="stylesheet" href="@Url.Action("Style","Home")" />
برای حالت فوق میتوانیم یک اکشن فیلتر به صورت زیر تهیه کنیم:
public class ContentType : ActionFilterAttribute { private string _contentType; public ContentType(string ct) { this._contentType = ct; } public override void OnActionExecuted(ActionExecutedContext context) { /* nada */ } public override void OnActionExecuting(ActionExecutingContext context) { context.HttpContext.Response.ContentType = this._contentType; } }
[ContentType("text/css")] public ActionResult Style() { var model = new Style { Color = "red", Background = "blue" }; return View(model); }
برای فایلهای JS نیز میتوانیم از یک View به عنوان محل قرارگیری کدهای جاوا اسکریپت استفاده کنیم:
public class JavaScriptSettingsController : Controller { public ActionResult Index() { return PartialView(); } }
$(function(){ $.post('@Url.Action("GetData", "Home")', function (data) { $('.notificationList').html(data); if ($(data).filter("li").length != 0) { $('#notificationCounter').html($(data).filter("li").length); } }); });
<script src="/JavaScriptSettings"></script>
این روش به خوبی برای ویوهای JS و CSS کار خواهد کرد؛ اما از آنجائیکه ویوی ما توسط ویژوال استودیو به عنوان یک فایل JS و یا CSS معتبر شناخته نمیشود، Intellisense برای آن مهیا نیست. برای فعال سازی Intellisense و همچنین معتبر شناخته شدن ویوی فوق، بهترین راهحل قرار دادن کدهای JS درون بلاک script است (برای فایلهای CSS نیز همینطور):
<script> $(function () { $.post('@Url.Action("Index", "Home")', function (data) { $('.notificationList').html(data); if ($(data).filter("li").length != 0) { $('#notificationCounter').html($(data).filter("li").length); } }); }); </script>
public class ExternalFileAttribute : ActionFilterAttribute { private readonly string _contentType; private readonly string _tag; public ExternalFileAttribute(string ct, string tag) { this._contentType = ct; _tag = tag; } public override void OnResultExecuted(ResultExecutedContext filterContext) { var response = filterContext.HttpContext.Response; response.Filter = new StripEnclosingTagsFilter(response.Filter, _tag); response.ContentType = _contentType; } private class StripEnclosingTagsFilter : MemoryStream { private static Regex _leadingOpeningScriptTag; private static Regex _trailingClosingScriptTag; //private static string Tag; private readonly StringBuilder _output; private readonly Stream _responseStream; /*static StripEnclosingTagsFilter() { LeadingOpeningScriptTag = new Regex(string.Format(@"^\s*<{0}[^>]*>", Tag), RegexOptions.Compiled); TrailingClosingScriptTag = new Regex(string.Format(@"</{0}>\s*$", Tag), RegexOptions.Compiled); }*/ public StripEnclosingTagsFilter(Stream responseStream, string tag) { _leadingOpeningScriptTag = new Regex(string.Format(@"^\s*<{0}[^>]*>", tag), RegexOptions.Compiled); _trailingClosingScriptTag = new Regex(string.Format(@"</{0}>\s*$", tag), RegexOptions.Compiled); _responseStream = responseStream; _output = new StringBuilder(); } public override void Write(byte[] buffer, int offset, int count) { string response = GetStringResponse(buffer, offset, count); _output.Append(response); } public override void Flush() { string response = _output.ToString(); if (_leadingOpeningScriptTag.IsMatch(response) && _trailingClosingScriptTag.IsMatch(response)) { response = _leadingOpeningScriptTag.Replace(response, string.Empty); response = _trailingClosingScriptTag.Replace(response, string.Empty); } WriteStringResponse(response); _output.Clear(); } private static string GetStringResponse(byte[] buffer, int offset, int count) { byte[] responseData = new byte[count]; Buffer.BlockCopy(buffer, offset, responseData, 0, count); return Encoding.Default.GetString(responseData); } private void WriteStringResponse(string response) { byte[] outdata = Encoding.Default.GetBytes(response); _responseStream.Write(outdata, 0, outdata.GetLength(0)); } } }
در نهایت میتوانیم اکشنمتد موردنظرمان را با فیلتر سفارشی مزین کنیم:
[ExternalFile("text/javascript", "script")] public ActionResult Index() { return PartialView(); }
[ExternalFile("text/css", "style")] public ActionResult Style() { var model = new Style { Color = "red", Background = "blue" }; return View(model); }