صفحات مودال در بوت استرپ 3
. . . <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title> .... </title> @Styles.Render("~/Content/css") @Scripts.Render("~/bundles/modernizr") <!--This bundle was moved by the Telerik VS Extensions for compatibility reasons--> @Scripts.Render("~/bundles/jquery") <link href="@Url.Content("~/Content/kendo/2013.3.1324/kendo.common.min.css")" rel="stylesheet" type="text/css" /> <link href="@Url.Content("~/Content/kendo/2013.3.1324/kendo.dataviz.min.css")" rel="stylesheet" type="text/css" /> <link href="@Url.Content("~/Content/kendo/2013.3.1324/kendo.bootstrap.min.css")" rel="stylesheet" type="text/css" /> <link href="@Url.Content("~/Content/kendo/2013.3.1324/kendo.dataviz.bootstrap.min.css")" rel="stylesheet" type="text/css" /> <link href="@Url.Content("~/Content/kendo/2013.3.1324/kendo.rtl.min.css")" rel="stylesheet" type="text/css" /> <script src="@Url.Content("~/Scripts/kendo/2013.3.1324/jquery.min.js")"></script> <script src="@Url.Content("~/Scripts/kendo/2013.3.1324/kendo.all.min.js")"></script> <script src="@Url.Content("~/Scripts/kendo/2013.3.1324/kendo.aspnetmvc.min.js")"></script> <script src="@Url.Content("~/Scripts/kendo.modernizr.custom.js")"></script> </head> <body> . . . . . @Scripts.Render("~/bundles/bootstrap") <script src="@Url.Content("~/Scripts/kendo/2013.3.1324/jquery.min.js")"></script> <script src="@Url.Content("~/Scripts/kendo/2013.3.1324/kendo.all.min.js")"></script> <script src="@Url.Content("~/Scripts/kendo/2013.3.1324/kendo.aspnetmvc.min.js")"></script> <script src="@Url.Content("~/Scripts/kendo.modernizr.custom.js")"></script> <script src="@Url.Content("~/Scripts/jquery.bootstrap-modal-ajax-form.js")"></script> <script src="@Url.Content("~/Scripts/jquery.bootstrap-modal-confirm.js")"></script> @RenderSection("scripts", required: false) </body> . . .
https://www.dntips.ir/(xxxxxx)/page.aspx
Response.ApplyAppPathModifier
Set-Cookie: <name>=<value>[; <Max-Age>=<age>] [; expires=<date>][; domain=<domain_name>] [; path=<some_path>][; secure][; HttpOnly]
<httpCookies httpOnlyCookies="true" …>
HttpCookie myCookie = new HttpCookie("myCookie"); myCookie.HttpOnly = true; Response.AppendCookie(myCookie);
تهیه میان افزار افزودن هدرهای Content Security Policy
کدهای کامل این میان افزار را در ادامه مشاهده میکنید:
using System.Threading.Tasks; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; namespace AngularTemplateDrivenFormsLab.Utils { public class ContentSecurityPolicyMiddleware { private readonly RequestDelegate _next; public ContentSecurityPolicyMiddleware(RequestDelegate next) { _next = next; } public Task Invoke(HttpContext context) { context.Response.Headers.Add("X-Frame-Options", "SAMEORIGIN"); context.Response.Headers.Add("X-Xss-Protection", "1; mode=block"); context.Response.Headers.Add("X-Content-Type-Options", "nosniff"); string[] csp = { "default-src 'self'", "style-src 'self' 'unsafe-inline'", "script-src 'self' 'unsafe-inline' 'unsafe-eval'", "font-src 'self'", "img-src 'self' data:", "connect-src 'self'", "media-src 'self'", "object-src 'self'", "report-uri /api/CspReport/Log" //TODO: Add api/CspReport/Log }; context.Response.Headers.Add("Content-Security-Policy", string.Join("; ", csp)); return _next(context); } } public static class ContentSecurityPolicyMiddlewareExtensions { /// <summary> /// Make sure you add this code BEFORE app.UseStaticFiles();, /// otherwise the headers will not be applied to your static files. /// </summary> public static IApplicationBuilder UseContentSecurityPolicy(this IApplicationBuilder builder) { return builder.UseMiddleware<ContentSecurityPolicyMiddleware>(); } } }
public void Configure(IApplicationBuilder app) { app.UseContentSecurityPolicy();
توضیحات تکمیلی
افزودن X-Frame-Options
context.Response.Headers.Add("X-Frame-Options", "SAMEORIGIN");
افزودن X-Xss-Protection
context.Response.Headers.Add("X-Xss-Protection", "1; mode=block");
افزودن X-Content-Type-Options
context.Response.Headers.Add("X-Content-Type-Options", "nosniff");
افزودن Content-Security-Policy
string[] csp = { "default-src 'self'", "style-src 'self' 'unsafe-inline'", "script-src 'self' 'unsafe-inline' 'unsafe-eval'", "font-src 'self'", "img-src 'self' data:", "connect-src 'self'", "media-src 'self'", "object-src 'self'", "report-uri /api/CspReport/Log" //TODO: Add api/CspReport/Log }; context.Response.Headers.Add("Content-Security-Policy", string.Join("; ", csp));
در اینجا ذکر unsafe-inline و unsafe-eval را مشاهده میکنید. برنامههای Angular به همراه شیوهنامههای inline و یا بکارگیری متد eval در مواردی خاص هستند. اگر این دو گزینه ذکر و فعال نشوند، در کنسول developer مرورگر، خطای بلاک شدن آنها را مشاهده کرده و همچنین برنامه از کار خواهد افتاد.
یک نکته: با فعالسازی گزینهی aot-- در حین ساخت برنامه، میتوان unsafe-eval را نیز حذف کرد.
استفاده از فایل web.config برای تعریف SameSite Cookies
یکی از پیشنهادهای اخیر ارائه شدهی جهت مقابلهی با حملات CSRF و XSRF، قابلیتی است به نام Same-Site Cookies. به این ترتیب مرورگر، کوکی سایت جاری را به همراه یک درخواست ارسال آن به سایت دیگر، پیوست نمیکند (کاری که هم اکنون با درخواستهای Cross-Site صورت میگیرد). برای رفع این مشکل، با این پیشنهاد امنیتی جدید، تنها کافی است SameSite، به انتهای کوکی اضافه شود:
Set-Cookie: sess=abc123; path=/; SameSite
نگارشهای بعدی ASP.NET Core، ویژگی SameSite را نیز به عنوان CookieOptions لحاظ کردهاند. همچنین یک سری از کوکیهای خودکار تولیدی توسط آن مانند کوکیهای anti-forgery به صورت خودکار با این ویژگی تولید میشوند.
اما مدیریت این مورد برای اعمال سراسری آن، با کدنویسی میسر نیست (مگر اینکه مانند نگارشهای بعدی ASP.NET Core پشتیبانی توکاری از آن صورت گیرد). به همین جهت میتوان از ماژول URL rewrite مربوط به IIS برای افزودن ویژگی SameSite به تمام کوکیهای تولید شدهی توسط سایت، کمک گرفت. برای این منظور تنها کافی است فایل web.config را ویرایش کرده و موارد ذیل را به آن اضافه کنید:
<?xml version="1.0" encoding="utf-8"?> <configuration> <system.webServer> <rewrite> <outboundRules> <clear /> <!-- https://scotthelme.co.uk/csrf-is-dead/ --> <rule name="Add SameSite" preCondition="No SameSite"> <match serverVariable="RESPONSE_Set_Cookie" pattern=".*" negate="false" /> <action type="Rewrite" value="{R:0}; SameSite=lax" /> <conditions></conditions> </rule> <preConditions> <preCondition name="No SameSite"> <add input="{RESPONSE_Set_Cookie}" pattern="." /> <add input="{RESPONSE_Set_Cookie}" pattern="; SameSite=lax" negate="true" /> </preCondition> </preConditions> </outboundRules> </rewrite> </system.webServer> </configuration>
لاگ کردن منابع بلاک شدهی توسط مرورگر در سمت سرور
اگر به هدر Content-Security-Policy دقت کنید، گزینهی آخر آن، ذکر اکشن متدی در سمت سرور است:
"report-uri /api/CspReport/Log" //TODO: Add api/CspReport/Log
{ "csp-report": { "document-uri": "http://localhost:5000/untypedSha", "referrer": "", "violated-directive": "script-src", "effective-directive": "script-src", "original-policy": "default-src 'self'; style-src 'self'; script-src 'self'; font-src 'self'; img-src 'self' data:; connect-src 'self'; media-src 'self'; object-src 'self'; report-uri /api/Home/CspReport", "disposition": "enforce", "blocked-uri": "eval", "line-number": 21, "column-number": 8, "source-file": "http://localhost:5000/scripts.bundle.js", "status-code": 200, "script-sample": "" } }
class CspPost { [JsonProperty("csp-report")] public CspReport CspReport { get; set; } } class CspReport { [JsonProperty("document-uri")] public string DocumentUri { get; set; } [JsonProperty("referrer")] public string Referrer { get; set; } [JsonProperty("violated-directive")] public string ViolatedDirective { get; set; } [JsonProperty("effective-directive")] public string EffectiveDirective { get; set; } [JsonProperty("original-policy")] public string OriginalPolicy { get; set; } [JsonProperty("disposition")] public string Disposition { get; set; } [JsonProperty("blocked-uri")] public string BlockedUri { get; set; } [JsonProperty("line-number")] public int LineNumber { get; set; } [JsonProperty("column-number")] public int ColumnNumber { get; set; } [JsonProperty("source-file")] public string SourceFile { get; set; } [JsonProperty("status-code")] public string StatusCode { get; set; } [JsonProperty("script-sample")] public string ScriptSample { get; set; } }
namespace AngularTemplateDrivenFormsLab.Controllers { [Route("api/[controller]")] public class CspReportController : Controller { [HttpPost("[action]")] [IgnoreAntiforgeryToken] public async Task<IActionResult> Log() { CspPost cspPost; using (var bodyReader = new StreamReader(this.HttpContext.Request.Body)) { var body = await bodyReader.ReadToEndAsync().ConfigureAwait(false); this.HttpContext.Request.Body = new MemoryStream(Encoding.UTF8.GetBytes(body)); cspPost = JsonConvert.DeserializeObject<CspPost>(body); } //TODO: log cspPost return Ok(); } } }
کدهای کامل این قسمت را از اینجا میتوانید دریافت کنید.
نگاهی به گزینههای مختلف مهیای جهت میزبانی SignalR
1) OWIN
2) ASP.NET Hosting
3) Self Hosting
4) Cloud و ویندوز Azure
1) OWIN
اگر به اسمبلیهای همراه با SignalR دقت کنید، یکی از آنها Microsoft.AspNet.SignalR.Owin.dll نام دارد. OWIN مخفف Open web server interface for .NET است و کار آن ایجاد لایهای بین وب سرورها و برنامههای وب میباشد. یکی از اهداف مهم آن ترغیب دنیای سورس باز به تهیه ماژولهای مختلف قابل استفاده در وب سرورهای دات نتی است. نکتهی مهمی که در SignalR و کلیه میزبانهای آن وجود دارد، بنا شدن تمامی آنها برفراز OWIN میباشد.
2) ASP.NET Hosting
بدون شک، میزبانی ASP.NET از هابهای SignalR، مرسومترین روش استفاده از این فناوری میباشد. این نوع میزبانی نیز برفراز OWIN بنا شده است. نصب آن توسط اجرای دستور پاور شل ذیل در یک پروژه وب صورت میگیرد:
PM> Install-Package Microsoft.AspNet.SignalR
3) خود میزبانی یا Self hosting
خود میزبانی نیز برفراز OWIN تهیه شده است و برای پیاده سازی آن نیاز است وابستگیهای مرتبط با آن، از طریق NuGet به کمک فرامین پاور شل ذیل دریافت شوند:
PM> Install-Package Microsoft.AspNet.SignalR.Owin PM> Install-Package Microsoft.Owin.Hosting -Pre PM> Install-Package Microsoft.Owin.Host.HttpListener -Pre
مراحل تهیه یک برنامه ثالث (برای مثال خارج از IIS یا یک وب سرور آزمایشی) به عنوان میزبان Hubs مورد نیاز به این شرح هستند:
الف) کلاس آغازین میزبان باید با پیاده سازی اینترفیسی به نام IAppBuilder تهیه شود.
ب) مسیریابیهای مورد نیاز تعریف گردند.
ج) وب سرور HTTP یا HTTPS توکار برای سرویس دهی آغاز گردد.
باید توجه داشت که در این حالت برخلاف روش ASP.NET Hosting، سایر اسمبلیهای برنامه جهت یافتن Hubهای تعریف شده، اسکن نمیشوند. همچنین هنگام کار با jQuery مباحث عنوان شده در مورد تنظیم دسترسیهای Cross domain نیز باید در اینجا اعمال گردند. به علاوه اجرای وب سرور توکار آن به دلایل امنیتی، نیاز به دسترسی مدیریتی دارد.
برای پیاده سازی یک نمونه، به برنامهای که تاکنون تهیه کردهایم، یک پروژه کنسول دیگر را به نام ConsoleHost اضافه کنید. البته باید درنظر داشت در دنیای واقعی این نوع برنامهها را عموما از نوع سرویسهای ویندوز NT تهیه میکنند.
در ادامه سه فرمان پاور شل یاد شده را برای افزودن وابستگیهای مورد نیاز فراخوانی نمائید. همچنین باید دقت داشت که این دستور بر روی پروژه جدید اضافه شده باید اجرا گردد.
using System; using Microsoft.AspNet.SignalR; using Microsoft.AspNet.SignalR.Hubs; using Microsoft.Owin.Hosting; using Owin; namespace SignalR02.ConsoleHost { public class Startup { public void Configuration(IAppBuilder app) { app.MapHubs(new HubConfiguration { EnableCrossDomain = true }); } } [HubName("chat")] public class ChatHub : Hub { public void SendMessage(string message) { var msg = string.Format("{0}:{1}", Context.ConnectionId, message); Clients.All.hello(msg); } } class Program { static void Main(string[] args) { using (WebApplication.Start<Startup>("http://localhost:1073/")) { Console.WriteLine("Press a key to terminate the server..."); Console.Read(); } } } }
سمت کلاینت استفاده از آن هیچ تفاوتی نمیکند و با جزئیات آن پیشتر آشنا شدهاید؛ برای مثال در کلاینت جیکوئری خاصیت connection.hub.url باید به مسیر جدید سرور هاب تنظیم گردد تا اتصالات به درستی برقرار شوند.
دریافت پروژه کامل مرتبط با این 4 قسمت (البته بدون فایلهای باینری آن، جهت کاهش حجم 32 مگابایتی)
SignalRSamples.zip
SignalR - قسمت دوم
با توجه به تصویر بالا SignalR در شرایط موجود بهترین روش برای برقراری ارتباط با سرور رو forever frame تشخیص داده و مشاهده میشه که این ارتباط دائمیه و فعلا نتیجهای از سمت سرور دریافت نکرده و ارتباط کاملا زنده است. البته اگر در این ابزار درباره درخواستهای ارسالی به سرور بیشتر جستجو بکنین اطلاعات بیشتری نصیبتون میشه که آوردنش اینجا بحث رو طولانی میکنه.
حالا برنامه رو در یه مرورگر دیگه که از html5 پشتیبانی میکنه اجرا کنین. مثلا نتیجه در گوگل کروم و ابزار توسعه اون به شکل زیره:
همونطور که میبینین در اینجا روش استفاده شده Server Sent Events هست.
در فایرفاکس هم با استفاده از ابزار محبوب firebug نتیجه مشابه کروم بدست میاد:
البته اگر علاقه زیادی به کندوکاو در جزئیات این درخواستها دارین (مثل خود من) چیزی بهتر از fiddler2 پیدا نمیشه. میتونین پس از ارسال یک متن دوباره این درخواستها رو مورد بررسی قرار بدین و ببینین که چیجوری کانالهای ارتباط پس از ارسال و دریافت دیتا قطع و برقرار میشه.
این نکته رو هم باید یادآور بشم که هرچند که این کتابخونه بهترین روش رو میتونه انتخاب کنه اما به برنامه نویس امکان تعیین صریح روش ارتباط رو هم میده. اگر به راهنماهای این کتابخونه سر بزنین میبینین که امکانات زیادی بهش اضافه شده و امکانات زیادی هم در آینده به اون اضافه میشه. امکاناتی از قبیل ارسال دادهها به یک کلاینت خاص و یا به گروهی خاص از کلاینتها، خصوصیسازی آدرس سرور و همچنین پشتیبانی از Cross Domain در آخرین نسخه، امکان استفاده از Reactive Extension (بلاگ)، بحث Self Hosting که امکان خیلی جالبیه و میتونه خیلی جاها یه عنوان یک راهحل سبک و سریع به کار بیاد، قابلیت فوق العاده در بایندینگ دادهها در سمت سرور و مخصوصا کلاینت، امکان تشخیص برقراری یا قطع ارتباط کلاینتها در سمت سرور، استفاده از امکانات این کتابخونه برای برقراری ارتباط با کلاینتها در خارج از فضای کلاسهای مشتق شده از دو کلاس پایه (Hub و PersistentConnection) و چند مورد دیگه تا نسخه جاری اضافه شدند.
درحال حاضر دارم روی یه برنامه چت با امکانات بیشتر کار میکنم که پس از آماده شدن ارائه میدمش. یکی از پروژههای متن بازی که با استفاده از این کتابخونه توسعه داده شده jabbr.net است. یه اتاق گفتگوی کامل با امکانات جالبه که میتونین به اون هم یه سری بزنین.
در آخر هم یه لینک جالب برای مطالعه معرفی میکنم: Highest voted Signalr Questions - stackoverflow