The first version of ASP.NET was released 17 years ago and during these years, it is fascinating to see how the ASP.NET team constructively reacted through these years to the major shifts happening on the web. Initially a platform that was closed and tried to hide and abstract the web; ASP.NET has metamorphized into an open source and cross platform - one that fully embraces the nature of the web. This is the first part of a series of 3 articles that will cover the history of ASP.NET from its launch to the latest ASP.NET Core releases.
Url Routing در ASP.Net WebForms
<%@ Application Language="C#" %> <script runat="server"> void Application_Start(object sender, EventArgs e) { // Code that runs on application startup System.Web.Routing.RouteTable.Routes.MapPageRoute("NewsDatils", "News/{pi}/{pt}", "~/ShowNews.aspx"); } void Application_End(object sender, EventArgs e) { // Code that runs on application shutdown }
بعد تو صفحه لینک اینجوری نوشتم
<asp:HyperLink ID="HyperLink2" runat="server" NavigateUrl='<%# string.Format("/News/{0}/{1}",Eval("NewsID"),Eval("NewsTitle").ToString().Replace(" ","-")) %>' Text='<%# Eval("NewsTitle") %>'></asp:HyperLink>
int p = int.Parse(Page.RouteData.Values["pi"].ToString());
Cannot use a leading .. to exit above the top directory.
در قسمت قبل « کار با اسکنر در برنامههای تحت وب (قسمت اول) » دیدی از کاری که قرار است انجام دهیم، رسیدیم. حالا سراغ یک پروژهی عملی و پیاده سازی مطالب مطرح شده میرویم.
ابتدا پروژهی WCF را شروع میکنیم. ویژوال استودیو را باز کرده و از قسمت New Project > Visual C# > WCF یک پروژهی WCF Service Application جدید را مثلا با نام "WcfServiceScanner" ایجاد نمایید. پس از ایجاد، دو فایل IService1.cs و Service1.scv موجود را به IScannerService و ScannerService تغییر نام دهید. سپس ابتدا محتویات کلاس اینترفیس IScannerService را به صورت زیر تعریف نمایید :
[ServiceContract] public interface IScannerService { [OperationContract] [WebInvoke(Method = "GET", BodyStyle = WebMessageBodyStyle.Wrapped, RequestFormat = WebMessageFormat.Json, ResponseFormat = WebMessageFormat.Json, UriTemplate = "GetScan")] string GetScan(); }
public class ScannerService : IScannerService { public string GetScan() { // TODO Add code here } }
بر روی پروژهی خود راست کلیک کرده و Add Reference را انتخاب نموده و سپس در قسمت COM، گزینهی Microsoft Windows Image Acquisition Library v2.0 را به پروژهی خود اضافه نمایید.
با اضافه شدن این ارجاع به پروژه، دسترسی به فضای نام WIA برای ما امکان پذیر میشود که ارجاعی از آن را در کلاس ScannerService قرار میدهیم.
using WIA;
public string GetScan() { var imgResult = String.Empty; var dialog = new CommonDialogClass(); try { // نمایش فرم پیشفرض اسکنر var image = dialog.ShowAcquireImage(WiaDeviceType.ScannerDeviceType); // ذخیره تصویر در یک فایل موقت var filename = Path.GetTempFileName(); image.SaveFile(filename); var img = Image.FromFile(filename); // img جهت ارسال سمت کاربر و نمایش در تگ Base64 تبدیل تصویر به imgResult = ImageHelper.ImageToBase64(img, ImageFormat.Jpeg); } catch { // از آنجاییه که امکان نمایش خطا وجود ندارد در صورت بروز خطا رشته خالی // بازگردانده میشود که به معنای نبود تصویر میباشد } return imgResult; }
CommonDialogClass کلاس اصلی در اینجا جهت نمایش فرم کار با اسکنر میباشد و متدهای مختلفی را جهت ارتباط با اسکنر در اختیار ما قرار میدهد که بسته به نیاز خود میتوانید از آنها استفاده کنید. برای نمونه در مثال ما نیز متد اصلی که مورد استفاده قرار گرفته، ShowAcquireImage میباشد که این متد، فرم پیش فرض دریافت اسکنر را به کاربر نمایش میدهد و کاربر از طریق آن میتواند قبل از شروع اسکن، یکسری تنظیمات را انجام دهد.
این متد ابتدا به صورت خودکار فرم تعیین دستگاه اسکنر ورودی را نمایش داده :
و سپس فرم پیش فرض اسکنرهای TWAIN را جهت تعیین تنظیمات اسکن نمایش میدهد که این امکان نیز در این فرم فراهم است تا دستگاههای Feeder یا Flated انتخاب گردند.
خروجی این متد همان عکس اسکن شده است که از نوع WIA.ImageFile میباشد و ما پس از دریافتش، ابتدا آن را در یک فایل موقت ذخیره نموده و سپس با استفاده از یک متد کمکی آن را به فرمت Base64 برای درخواست کننده اسکن ارسال مینماییم.
کدهای کلاس کمکی ImageHelper:
public static string ImageToBase64(Image image, System.Drawing.Imaging.ImageFormat format) { if (image != null) { using (MemoryStream ms = new MemoryStream()) { // Convert Image to byte[] image.Save(ms, format); byte[] imageBytes = ms.ToArray(); // Convert byte[] to Base64 String string base64String = Convert.ToBase64String(imageBytes); return base64String; } } return String.Empty; }
این مثال به سادهترین شکل نوشته شد. کلاس دیگری هم در اینجا وجود دارد و در صورتیکه از اسکنر نوع Feeder استفاده میکنید، میتوانید از کدهای آن استفاده کنید.
جهت رفع این خطا، در قسمت Referenceهای پروژه خود، WIA را انتخاب نموده و از Propertiesهای آن خصوصیت Embed Interop Types را به False تغییر دهید؛ مشکل حل میشود.
به سراغ پروژهی ویندوز فرم جهت هاست کردن این WCF سرویس میرویم. میتوانید این سرویس را بر روی یک Console App یا Windows Service هم هاست کنید که در اینجا برای سادگی مثال، از WinForm استفاده میکنیم.
یک پروژهی WinForm جدید را ایجاد کنید و سپس از قسمت Add Reference > Solution به مسیر پروژهی قبلی رفته و dllهای آن را به پروژه خود اضافه نمایید.
Form1.cs را باز کرده و ابتدا دو متغیر زیر را در آن به صورت عمومی تعریف نمایید:
private readonly Uri _baseAddress = new Uri("http://localhost:6019"); private ServiceHost _host;
حال در رویداد Form_Load برنامه، کدهای زیر را جهت هاست کردن سرویس اضافه مینماییم:
private void Form1_Load(object sender, EventArgs e) { _host = new ServiceHost(typeof(WcfServiceScanner.ScannerService), _baseAddress); _host.Open(); } private void Form1_FormClosing(object sender, FormClosingEventArgs e) { _host.Close(); }
فایل App.Config پروژهی WinForm را باز کرده و کدهای آنرا مطابق زیر تغییر دهید:
<?xml version="1.0" encoding="utf-8" ?> <configuration> <startup> <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" /> </startup> <system.serviceModel> <behaviors> <serviceBehaviors> <behavior name="BehaviourMetaData"> <serviceMetadata httpGetEnabled="true" /> </behavior> </serviceBehaviors> </behaviors> <services> <service name="WcfServiceScanner.ScannerService" behaviorConfiguration="BehaviourMetaData"> <endpoint address="" binding="basicHttpBinding" contract="WcfServiceScanner.IScannerService" /> </service> </services> </system.serviceModel> </configuration>
اگر موفق به اجرا نشدید و احیانا با خطای زیر مواجه شدید، اطمینان حاصل کنید که ویژوال استودیو Run as Administrator باشد. مشکل حل خواهد شد.
به سراغ پروژهی بعدی، یعنی وب سایت خود میرویم. یک پروژهی MVC جدید ایجاد نمایید و در View مورد نظر خود، کدهای زیر را جهت صدا زدن متد GetScan اضافه میکنیم.
( از آنجا که کدها به صورت جاوا اسکریپت میباشد، پس مهم نیست که حتما پروژه MVC باشد؛ یک صفحهی HTML ساده هم کافی است).
<a href="#" id="get-scan">Get Scan</a> <img src="" id="img-scanned" /> <script> $("#get-scan").click(function () { var url = 'http://localhost:6019/'; $.get(url, function (data) { $("#img-scanned").attr("src","data:image/Jpeg;base64, "+ data.GetScanResult); }); }); </script>
راه حلهای زیادی برای این مشکل ارائه شده است، و متاسفانه بسیاری از آنها در شرایط پروژهی ما جوابگو نمیباشد (به دلیل هاست روی یک پروژه ویندوزی). تنها راه حل مطمئن (تست شده) استفاده از یک کلاس سفارشی در پروژهی WCF Service میباشد که مثال آن در اینجا آورده شده است.
برای رفع مشکل به پروژه WcfServiceScanner بازگشته و کلاس جدیدی را به نام CORSSupport ایجاد کرده و کدهای زیر را به آن اضافه کنید:
public class CORSSupport : IDispatchMessageInspector { Dictionary<string, string> requiredHeaders; public CORSSupport(Dictionary<string, string> headers) { requiredHeaders = headers ?? new Dictionary<string, string>(); } public object AfterReceiveRequest(ref System.ServiceModel.Channels.Message request, System.ServiceModel.IClientChannel channel, System.ServiceModel.InstanceContext instanceContext) { var httpRequest = request.Properties["httpRequest"] as HttpRequestMessageProperty; if (httpRequest.Method.ToLower() == "options") instanceContext.Abort(); return httpRequest; } public void BeforeSendReply(ref System.ServiceModel.Channels.Message reply, object correlationState) { var httpResponse = reply.Properties["httpResponse"] as HttpResponseMessageProperty; var httpRequest = correlationState as HttpRequestMessageProperty; foreach (var item in requiredHeaders) { httpResponse.Headers.Add(item.Key, item.Value); } var origin = httpRequest.Headers["origin"]; if (origin != null) httpResponse.Headers.Add("Access-Control-Allow-Origin", origin); var method = httpRequest.Method; if (method.ToLower() == "options") httpResponse.StatusCode = System.Net.HttpStatusCode.NoContent; } } // Simply apply this attribute to a DataService-derived class to get // CORS support in that service [AttributeUsage(AttributeTargets.Class)] public class CORSSupportBehaviorAttribute : Attribute, IServiceBehavior { #region IServiceBehavior Members void IServiceBehavior.AddBindingParameters(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase, System.Collections.ObjectModel.Collection<ServiceEndpoint> endpoints, BindingParameterCollection bindingParameters) { } void IServiceBehavior.ApplyDispatchBehavior(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase) { var requiredHeaders = new Dictionary<string, string>(); //Chrome doesn't accept wildcards when authorization flag is true //requiredHeaders.Add("Access-Control-Allow-Origin", "*"); requiredHeaders.Add("Access-Control-Request-Method", "POST,GET,PUT,DELETE,OPTIONS"); requiredHeaders.Add("Access-Control-Allow-Headers", "Accept, Origin, Authorization, X-Requested-With,Content-Type"); requiredHeaders.Add("Access-Control-Allow-Credentials", "true"); foreach (ChannelDispatcher cd in serviceHostBase.ChannelDispatchers) { foreach (EndpointDispatcher ed in cd.Endpoints) { ed.DispatchRuntime.MessageInspectors.Add(new CORSSupport(requiredHeaders)); } } } void IServiceBehavior.Validate(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase) { } #endregion }
using System.ServiceModel; using System.ServiceModel.Channels; using System.ServiceModel.Description; using System.ServiceModel.Dispatcher;
[CORSSupportBehavior] public class ScannerService : IScannerService {
کار تمام است، یکبار دیگر ابتدا پروژهی WcfServiecScanner و سپس پروژه هاست را Build کرده و برنامهی هاست را اجرا کنید. اکنون مشاهده میکنید که با زدن دکمهی اسکن، اسکنر فرم تنظیمات اسکن را نمایش میدهد که پس از زدن دکمهی Scan، پروسه آغاز شده و پس از اتمام، تصویر اسکن شده در صفحهی وب سایت نمایش داده میشود.
سؤال: آیا میشود دسترسی به فایلهای قرار گرفته در این پوشه عمومی را کنترل کرد؟ به نحوی که فقط کاربران عضو سایت پس از اعتبارسنجی بتوانند آنها را دریافت کنند؟
پاسخ: شاید عنوان کنید که میتوان از تگ location در فایل web.config برای اینکار استفاده کرد:
<location path="Export"> <system.web> <authorization> <deny users="?" /> </authorization> </system.web> </location>
سؤال: آیا راه حلی وجود دارد که بتوان فایلهای استاتیک را صرفنظر از نوع، نگارش و حالت اجرای IIS توسط موتور ASP.NET مدیریت کرد؟
پاسخ: بلی. در ASP.NET MVC با تنظیم یک سطر ذیل، اینکار انجام میشود:
public static void RegisterRoutes(RouteCollection routes) { // ... routes.RouteExistingFiles = true; // ... }
RouteCollection در ASP.NET MVC به کمک امکانات MapPathBasedVirtualPathProvider خود، ابتدا درخواست رسیده را بررسی میکند. اگر این درخواست به یک فایل عمومی اشاره کند، کل سیستم مسیریابی را غیرفعال میکند. اما با تنظیم RouteExistingFiles دیگر این بررسی صورت نخواهد گرفت (به عبارتی در بالا بردن سرعت نمایشی سایت نیز تاثیر گذار خواهد بود؛ چون یکی از بررسیها را حذف میکند).
ایجاد کنترلری به نام پوشهای که قرار است محافظت شود
نام پوشه قرار گرفته در ریشه سایت، Export است. بنابراین برای هدایت درخواستهای رسیده به آن (پس از تنظیم فوق)، نیاز است یک کنترلر جدید را به نام Export نیز ایجاد کنیم:
using System.IO; using System.Web.Mvc; namespace Mvc4RouteExistingFiles.Controllers { public class ExportController : Controller { public ActionResult Index(string id) { if (string.IsNullOrWhiteSpace(id)) { return Redirect("/"); } var fileName= Path.GetFileName(id); var path = Server.MapPath("~/export/"+ fileName); return File(path, System.Net.Mime.MediaTypeNames.Application.Octet, fileName); } } }
برای اینکه بررسی کنیم، آیا واقعا فایلهای استاتیک قرار گرفته در پوشه Export به این کنترلر هدایت میشود یا خیر، یک breakpoint را بر روی سطر اول اکشن متد Index قرار میدهیم. برنامه را اجرا کنید ... کار نخواهد کرد، زیرا مسیر یک فایل فرضی به صورت ذیل:
http://localhost/export/test.pdf
برای حل این مشکل فقط کافی است مسیر یابی متناظری را تعریف کنیم:
using System.Web.Mvc; using System.Web.Routing; namespace Mvc4RouteExistingFiles { public class RouteConfig { public static void RegisterRoutes(RouteCollection routes) { routes.IgnoreRoute("{resource}.axd/{*pathInfo}"); routes.RouteExistingFiles = true; routes.MapRoute( name: "ExportRoute", url: "Export/{id}", defaults: new { controller = "Export", action = "Index", id = UrlParameter.Optional } ); routes.MapRoute( name: "Default", url: "{controller}/{action}/{id}", defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional } ); } } }
اینبار اگر برنامه را اجرا کنیم، breakpoint ما کار خواهد کرد:
تنظیمات ثانویه پس از فعال سازی RouteExistingFiles
در این حالت با فعال سازی مسیریابی فایلهای موجود، دیگر هیچ فایل استاتیکی به صورت معمول در اختیار کاربران قرار نخواهد گرفت و اگر همانند توضیحات قبل برای آنها کنترلر جداگانهای را تهیه نکنیم، عملا سایت از کار خواهد افتاد.
برای رفع این مشکل، در ابتدای متد RegisterRoutes فوق، تنظیمات ذیل را اضافه کنید تا پوشههای content، scripts و همچنین یک سری فایل با پسوند مشخص، همانند سابق و مستقیما توسط سرور ارائه شوند؛ در غیراینصورت کاربر پیغام 404 را پس از درخواست آنها، دریافت خواهد کرد:
routes.IgnoreRoute("{resource}.axd/{*pathInfo}"); routes.IgnoreRoute("content/{*pathInfo}"); routes.IgnoreRoute("scripts/{*pathInfo}"); routes.IgnoreRoute("favicon.ico"); routes.IgnoreRoute("{resource}.ico"); routes.IgnoreRoute("{resource}.png"); routes.IgnoreRoute("{resource}.jpg"); routes.IgnoreRoute("{resource}.gif"); routes.IgnoreRoute("{resource}.txt");
ASP.NET MVC #18
- برای استفاده از asp.net identity با روش database first دو سری ویدیو در یوتیوب هست: (^ و ^)
مطابق با ویکی پدیا، سطوح
دسترسی مشخص میکند که کدام کاربران یا سیستم پردازش اجازه دسترسی به اشیاء را
دارند(Authentication)، همچنین چه عملیاتهایی بر روی اشیاء
مجازند که اجرا شوند(Authorization).
در مورد جوملا، ما دو جنبه جدا برای سطوح دسترسی
داریم:
1.
کدام کاربران به چه بخشهایی میتوانند دسترسی
داشته باشند؟ برای مثال، انتخاب یک منو برای کدام کاربر فعال خواهد بود؟
2.
چه عملیات (یا اقداماتی) کاربر میتواند بر روی اشیاء
داشته باشد؟ برای مثال، آیا کاربر میتواند یک مطلب را ارسال یا ویرایش کند؟
ماهیتهای موجود در سیستم :
·
کاربران
کاربر
میتواند به گروههای مختلفی اختصاص یابد.
·
گروهها کاربری
شامل
مجوزهایی به صورت پیش فرض میباشند که این مجوزها را از سطوح بالایی نیز به ارث میبرند.
·
سطوح دسترسی
شامل
یک یا چند گروه کاربری میباشد و سطوح دسترسی به محتواهای سایت نسبت داده میشود
یعنی اگر یک مطلب دارای سطح دسترسی عمومی باشد آنگاه تمامی گروههای کاربری
که در عمومی وجود دارند میتوانند مطلب را مشاهده کنند.
·
عملیات و مجوزها
به
صورت پیش فرض یک سری عملیات در سیستم تعریف شده است شامل ویرایش ، حذف و غیره که
برای هر گروه کاربری (تعدادی گروه کاربری به صورت پیش فرض در سیستم تعریف شده است)
به صورت پیش فرض مجوزهایی در نظر گرفته شده است که این مجوزها قابلیت ارث بری از
والد گروه به فرزند رانیز دارا میباشد پس با این حساب همیشه در جوملا والد از سطح
دسترسی پایینتری نسبت به فرزند برخوردار میباشد.
اما
باید گفت در جوملا به ازای هر
کامپوننت نیز میتوان این مجوزها را به ازای گروههای مختلف تغییر داد در این جا
هم هر کامپوننت دارای مجوزهای پیش فرضی میباشد که در هنگام نصب کامپوننت برای
آن در نظر گرفته میشود.
جداول این سیستم :
: users جدول کاربران
usergroups : جدول گروههای کاری یا همان نقشهای کاربری
user_usergroup_map : جدول واسط بین کاربران و گروههای کاری به منظور ایجاد
رابطهی چند به چند (n:n)
assets : این جدول که از جوملا 1.6 به بعد به دیتابیس جوملا افزوده
شده است مهمترین جدول در این سیستم میباشد . که در آن به ازای هر جز که سطح
دسترسی باید برای آن لحاظ گردد یک سطر در نظر گرفته میشود که این سطر باتوجه به
افزایش اجزا سیستم تغییر و به صورت داینامیک به جدول اضافه میگردد ضمنا این سطور
قابلیت ارث بری از یکدیگر را نیز دارا میباشند. در هر بک از سطرها فیلدی به نام rulsوجود دارد محتوای این فیلد از نوع داده ای json میباشد با یک
مثال شاید بهتر بتوان توضیح داد :
محتوای
فیلد کامپوننت بنر :
{"core.admin":{"9":1,"7":1},"core.manage":{"6":1},"core.create":[],"core.delete":[],"core.edit}
در این جا “core.admin” مجوز دسترسی مدیریتی به این کامپوننت میباشد که
گروههای کاری شماره 7 و 9 دارای چنین دسترسی میباشند . ضمنا عملیاتهای "core.create" از سطوح
بالاتر یا همان سطر والد خود ارث بری میکند.
Viewlevel : در این جدول سطوح دسترسی تعریف شده اند مهمترین فیلد این
جدول نیز rulsنام دارد و حاوی id گروه
هایی است که به این سطح دسترسی ، دسترسی دارند.
به طور مثال سطح دسترسی ثبت نام شده حاوی [6,2,8] میباشد یعنی گروههای کاری با idهای مورد نظر میتوانند به محتواهای با سطح دسترسی ثبت نام شده دسترسی داشته باشند.
دیاگرام جداول :