Task.Factory.StartNew(Sub() work()) 'نحوهی صحیح نوشتنش Task.Factory.StartNew(AddressOf work) '---- یا Task.Factory.StartNew(Sub() While True End While End Sub)
C# 12.0 - Primary Constructors
private string <FirstName>k__BackingField;
public class AbcController : Controller { private readonly IMyService _myService; public AbcController(IMyService myService) { _myService = myService; } }
public class MyController(IMyService _myService) : ControllerBase { [HttpGet] public IActionResult RandomNumber() { var number = _myService.RandomNumber(); _myService = null; var number2 = MyRandom(); return Ok(number + number2); } public int MyRandom() { return _myService.RandomNumber(); } }
Angular CLI - قسمت اول - نصب و راه اندازی
npm ERR! Windows_NT 10.0.10586 npm ERR! argv "C:\\Program Files\\nodejs\\node.exe" "C:\\Program Files\\nodejs\\node_modules\\npm\\bin\\npm-cli.js" "install" "-g" "@angular/cli" npm ERR! node v7.10.0 npm ERR! npm v4.2.0 npm ERR! Cannot read property 'path' of null npm ERR! npm ERR! If you need help, you may report this error at: npm ERR! <https://github.com/npm/npm/issues> npm ERR! Please include the following file with any support request: npm ERR! C:\Users\payervand\AppData\Roaming\npm-cache\_logs\2017-05-13T05_43_04_935Z-debug.log
نیاز به علامتگذاری خواصی که باید رمزنگاری شوند
میخواهیم خاصیت یا خاصیتهای مشخصی، از یک مدل را رمزنگاری شده به سمت کلاینت ارسال کنیم. به همین جهت ویژگی خالی زیر را به پروژه اضافه میکنیم تا از آن تنها جهت علامتگذاری این نوع خواص، استفاده کنیم:
using System; namespace EncryptedModelBinder.Utils { [AttributeUsage(AttributeTargets.Property, AllowMultiple = false)] public class EncryptedFieldAttribute : Attribute { } }
رمزنگاری خودکار مدل خروجی از یک اکشن متد
در ادامه کدهای کامل یک ResultFilter را مشاهده میکنید که مدل ارسالی به سمت کلاینت را یافته و سپس خواصی از آنرا که با ویژگی EncryptedField مزین شدهاند، به صورت خودکار رمزنگاری میکند:
namespace EncryptedModelBinder.Utils { public class EncryptedFieldResultFilter : ResultFilterAttribute { private readonly IProtectionProviderService _protectionProviderService; private readonly ILogger<EncryptedFieldResultFilter> _logger; private readonly ConcurrentDictionary<Type, bool> _modelsWithEncryptedFieldAttributes = new ConcurrentDictionary<Type, bool>(); public EncryptedFieldResultFilter( IProtectionProviderService protectionProviderService, ILogger<EncryptedFieldResultFilter> logger) { _protectionProviderService = protectionProviderService; _logger = logger; } public override void OnResultExecuting(ResultExecutingContext context) { var model = context.Result switch { PageResult pageResult => pageResult.Model, // For Razor pages ViewResult viewResult => viewResult.Model, // For MVC Views ObjectResult objectResult => objectResult.Value, // For Web API results _ => null }; if (model is null) { return; } if (typeof(IEnumerable).IsAssignableFrom(model.GetType())) { foreach (var item in model as IEnumerable) { encryptProperties(item); } } else { encryptProperties(model); } } private void encryptProperties(object model) { var modelType = model.GetType(); if (_modelsWithEncryptedFieldAttributes.TryGetValue(modelType, out var hasEncryptedFieldAttribute) && !hasEncryptedFieldAttribute) { return; } foreach (var property in modelType.GetProperties()) { var attribute = property.GetCustomAttributes(typeof(EncryptedFieldAttribute), false).FirstOrDefault(); if (attribute == null) { continue; } hasEncryptedFieldAttribute = true; var value = property.GetValue(model); if (value is null) { continue; } if (value.GetType() != typeof(string)) { _logger.LogWarning($"[EncryptedField] should be applied to `string` proprties, But type of `{property.DeclaringType}.{property.Name}` is `{property.PropertyType}`."); continue; } var encryptedData = _protectionProviderService.Encrypt(value.ToString()); property.SetValue(model, encryptedData); } _modelsWithEncryptedFieldAttributes.TryAdd(modelType, hasEncryptedFieldAttribute); } } }
- در اینجا برای رمزنگاری از IProtectionProviderService استفاده شدهاست که در بستهی DNTCommon.Web.Core تعریف شدهاست. این سرویس در پشت صحنه از سیستم Data Protection استفاده میکند.
- سپس رخداد OnResultExecuting، بازنویسی شدهاست تا بتوان به مدل ارسالی به سمت کلاینت، پیش از ارسال نهایی آن، دسترسی یافت.
- context.Result میتواند از نوع PageResult صفحات Razor باشد و یا از نوع ViewResult مدلهای متداول Viewهای پروژههای MVC و یا از نوع ObjectResult که مرتبط است به پروژههای Web Api بدون هیچ نوع View سمت سروری. هر کدام از این نوعها، دارای خاصیت مدل هستند که در اینجا قصد بررسی آنرا داریم.
- پس از مشخص شدن شیء Model، اکنون حلقهای را بر روی خواص آن تشکیل داده و خواصی را که دارای ویژگی EncryptedFieldAttribute هستند، یافته و آنها را رمزنگاری میکنیم.
روش اعمال این فیلتر باید به صورت سراسری باشد:
namespace EncryptedModelBinder { public class Startup { public void ConfigureServices(IServiceCollection services) { services.AddDNTCommonWeb(); services.AddControllersWithViews(options => { options.Filters.Add(typeof(EncryptedFieldResultFilter)); }); }
رمزگشایی خودکار مدل دریافتی از سمت کلاینت
تا اینجا موفق شدیم خواص ویژهای از مدلها را رمزنگاری کنیم. مرحلهی بعد، رمزگشایی خودکار این اطلاعات در سمت سرور است. به همین جهت نیاز داریم تا در سیستم Model Binding پیشفرض ASP.NET Core مداخله کرده و منطق سفارشی خود را تزریق کنیم. بنابراین در ابتدا یک IModelBinderProvider سفارشی را تهیه میکنیم تا در صورتیکه خاصیت جاری در حال بررسی توسط سیستم Model Binding دارای ویژگی EncryptedFieldAttribute بود، از EncryptedFieldModelBinder برای پردازش آن استفاده کند:
namespace EncryptedModelBinder.Utils { public class EncryptedFieldModelBinderProvider : IModelBinderProvider { public IModelBinder GetBinder(ModelBinderProviderContext context) { if (context == null) { throw new ArgumentNullException(nameof(context)); } if (context.Metadata.IsComplexType) { return null; } var propName = context.Metadata.PropertyName; if (string.IsNullOrWhiteSpace(propName)) { return null; } var propInfo = context.Metadata.ContainerType.GetProperty(propName); if (propInfo == null) { return null; } var attribute = propInfo.GetCustomAttributes(typeof(EncryptedFieldAttribute), false).FirstOrDefault(); if (attribute == null) { return null; } return new BinderTypeModelBinder(typeof(EncryptedFieldModelBinder)); } } }
namespace EncryptedModelBinder.Utils { public class EncryptedFieldModelBinder : IModelBinder { private readonly IProtectionProviderService _protectionProviderService; public EncryptedFieldModelBinder(IProtectionProviderService protectionProviderService) { _protectionProviderService = protectionProviderService; } public Task BindModelAsync(ModelBindingContext bindingContext) { if (bindingContext == null) { throw new ArgumentNullException(nameof(bindingContext)); } var logger = bindingContext.HttpContext.RequestServices.GetRequiredService<ILoggerFactory>(); var fallbackBinder = new SimpleTypeModelBinder(bindingContext.ModelType, logger); var valueProviderResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName); if (valueProviderResult == ValueProviderResult.None) { return fallbackBinder.BindModelAsync(bindingContext); } bindingContext.ModelState.SetModelValue(bindingContext.ModelName, valueProviderResult); var valueAsString = valueProviderResult.FirstValue; if (string.IsNullOrWhiteSpace(valueAsString)) { return fallbackBinder.BindModelAsync(bindingContext); } var decryptedResult = _protectionProviderService.Decrypt(valueAsString); bindingContext.Result = ModelBindingResult.Success(decryptedResult); return Task.CompletedTask; } } }
پس از این تعاریف نیاز است EncryptedFieldModelBinderProvider را به صورت زیر به سیستم معرفی کرد:
namespace EncryptedModelBinder { public class Startup { public void ConfigureServices(IServiceCollection services) { services.AddDNTCommonWeb(); services.AddControllersWithViews(options => { options.ModelBinderProviders.Insert(0, new EncryptedFieldModelBinderProvider()); options.Filters.Add(typeof(EncryptedFieldResultFilter)); }); }
یک مثال
فرض کنید مدلهای زیر تعریف شدهاند:
namespace EncryptedModelBinder.Models { public class ProductInputModel { [EncryptedField] public string Id { get; set; } [EncryptedField] public int Price { get; set; } public string Name { get; set; } } } namespace EncryptedModelBinder.Models { public class ProductViewModel { [EncryptedField] public string Id { get; set; } [EncryptedField] public int Price { get; set; } public string Name { get; set; } } }
اکنون کنترلر زیر زمانیکه رندر شود، View متناظر با اکشن متد Index آن، یکسری لینک را به اکشن متد Details، جهت مشاهدهی جزئیات محصول، تولید میکند. همچنین اکشن متد Products آن هم فقط یک خروجی JSON را به همراه دارد:
namespace EncryptedModelBinder.Controllers { public class HomeController : Controller { public IActionResult Index() { var model = getProducts(); return View(model); } public ActionResult<string> Details(ProductInputModel model) { return model.Id; } public ActionResult<List<ProductViewModel>> Products() { return getProducts(); } private static List<ProductViewModel> getProducts() { return new List<ProductViewModel> { new ProductViewModel { Id = "1", Name = "Product 1"}, new ProductViewModel { Id = "2", Name = "Product 2"}, new ProductViewModel { Id = "3", Name = "Product 3"} }; } } }
@model List<ProductViewModel> <h3>Home</h3> <ul> @foreach (var item in Model) { <li><a asp-action="Details" asp-route-id="@item.Id">@item.Name</a></li> } </ul>
و اگر یکی از لینکها را درخواست کنیم، خروجی model.Id، به صورت معمولی و رمزگشایی شدهای مشاهده میشود (این خروجی یک رشتهاست که هیچ ویژگی خاصی به آن اعمال نشدهاست. به همین جهت، اینبار این خروجی معمولی مشاهده میشود). هدف از اکشن متد Details، نمایش رمزگشایی خودکار اطلاعات است.
و یا اگر اکشن متدی که همانند اکشن متدهای Web API، فقط یک شیء JSON را باز میگرداند، فراخوانی کنیم نیز میتوان به خروجی رمزنگاری شدهی زیر رسید:
کدهای کامل این مطلب را از اینجا میتوانید دریافت کنید: EncryptedModelBinder.zip
وهله سازی یک کلاس موجود توسط Reflection.Emit
کدهای کامل مثالی را در این زمینه در ادامه ملاحظه میکنید:
using System; using System.Reflection.Emit; namespace FastReflectionTests { public class Order { public string Name { set; get; } public Order() { Name = "Order01"; } } class Program { static void Main(string[] args) { var myMethod = new DynamicMethod(name: "myMethod", returnType: typeof(Order), parameterTypes: Type.EmptyTypes, m: typeof(Program).Module); var il = myMethod.GetILGenerator(); il.Emit(OpCodes.Newobj, typeof(Order).GetConstructor(Type.EmptyTypes)); il.Emit(OpCodes.Ret); var getOrderMethod = (Func<Order>)myMethod.CreateDelegate(typeof(Func<Order>)); Console.WriteLine(getOrderMethod().Name); } } }
سپس با دسترسی به ILGenerator سعی خواهیم کرد تا وهله جدیدی را از کلاس Order ایجاد کنیم. برای این منظور باید از OpCode جدیدی به نام Newobj استفاده کنیم که مخفف new object است. این OpCode برای عملکرد خود، نیاز به دریافت اشارهگری به سازنده کلاسی دارد که قرار است آنرا وهله سازی کند. در اینجا با Ret، کار متد را خاتمه داده و در ادامه برای استفاده از آن تنها کافی است یک delegate را ایجاد نمائیم.
بنابراین به مجموعه متدهای سریع خود، متد ذیل را نیز میتوان افزود:
public static Func<T> CreatFastObjectInstantiater<T>() { var t = typeof(T); var ctor = t.GetConstructor(Type.EmptyTypes); if (ctor == null) return null; var dynamicCtor = new DynamicMethod("_", t, Type.EmptyTypes, t, true); var il = dynamicCtor.GetILGenerator(); il.Emit(OpCodes.Newobj, ctor); il.Emit(OpCodes.Ret); return (Func<T>)dynamicCtor.CreateDelegate(typeof(Func<T>)); }
اگر علاقمند بودید که سرعت این روش را با روش متداول Activator.CreateInstance مقایسه کنید، مطلب زیر بسیار مفید است:
Creating objects - Perf implications
یک کاربرد مهم این مساله در نوشتن ORM مانندهایی است که قرار است لیستی جنریک را خیلی سریع تولید کنند؛ از این جهت که در حلقه DataReader آنها مدام نیاز است یک وهله جدید از شیء مدنظر ایجاد و مقدار دهی شود:
Mapping Datareader to Objects Using Reflection.Emit
تهیه کلاس SimpleInterceptor
برای اتصال به متدهای اجرای دستورات SQL در EF 6 تنها کافی است یک کلاس جدید را از کلاس پایه DbCommandInterceptor مشتق کرده و سپس متدهای کلاس پایه را override کنیم. در این متدها، فراخوانی متدهای کلاس پایه، معادل خواهند بود با اجرای واقعی دستور بر روی بانک اطلاعاتی. به این ترتیب حتی میتوان مدت زمان انجام عملیات را نیز بدست آورد. در اینجا command.CommandText معادل است با دستور SQL در حال اجرا و همچنین نیاز است تا تمام سطوح تو در توی استثناهای احتمالی رخ داده را نیز بررسی کرد:
using System; using System.Data.Common; using System.Data.Entity.Infrastructure.Interception; using System.Diagnostics; using System.Text; namespace EFCommandInterception { public class SimpleInterceptor : DbCommandInterceptor { public override void ScalarExecuting(DbCommand command, DbCommandInterceptionContext<object> interceptionContext) { var timespan = runCommand(() => base.ScalarExecuting(command, interceptionContext)); logData(command, interceptionContext.Exception, timespan); } public override void NonQueryExecuting(DbCommand command, DbCommandInterceptionContext<int> interceptionContext) { var timespan = runCommand(() => base.NonQueryExecuting(command, interceptionContext)); logData(command, interceptionContext.Exception, timespan); } public override void ReaderExecuting(DbCommand command, DbCommandInterceptionContext<DbDataReader> interceptionContext) { var timespan = runCommand(() => base.ReaderExecuting(command, interceptionContext)); logData(command, interceptionContext.Exception, timespan); } private static Stopwatch runCommand(Action command) { var timespan = Stopwatch.StartNew(); command(); timespan.Stop(); return timespan; } private static void logData(DbCommand command, Exception exception, Stopwatch timespan) { if (exception != null) { Trace.TraceError(formatException(exception, "Error executing command: {0}", command.CommandText)); } else { Trace.TraceInformation(string.Concat("Elapsed time: ", timespan.Elapsed, " Command: ", command.CommandText)); } } private static string formatException(Exception exception, string fmt, params object[] vars) { var sb = new StringBuilder(); sb.Append(string.Format(fmt, vars)); sb.Append(" Exception: "); sb.Append(exception.ToString()); while (exception.InnerException != null) { sb.Append(" Inner exception: "); sb.Append(exception.InnerException.ToString()); exception = exception.InnerException; } return sb.ToString(); } } }
کلاس فوق را کافی است تنها یکبار در آغاز برنامه (مثلا در متد Application_Start برنامههای وب) به EF 6 معرفی کرد:
DbInterception.Add(new SimpleInterceptor());
<?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); } } } }
OpenCVSharp #6
در ضمن چون چند لحظه زمان میبرد تا وب کم راه اندازی شود من قبل از کد زیر
while (capture.QueryFrame() != null) { ... }
while (capture.QueryFrame() == null) { }
SQL Antipattern #1
بخش اول : Jaywalking
در این بخش در حال توسعه ویژگی نرم افزاری هستیم که در آن هرکاربر به عنوان یک کاربر اصلی برای یک محصول تخصیص داده میشود. در طراحی اصلی ما فقط اجازه میدهیم یک کاربر متعلق به هر محصول باشد، اما امکان چنین تقاضایی وجود دارد که چند کاربر نیز به یک محصول اختصاص داده شوند.
در این صورت، اگر پایگاه داده را به نحوی تغییر دهیم که شناسهی حساب کاربران را در لیستی که با کاما از یکدیگر جدا شدهاند ذخیره نماییم، خیلی سادهتر به نظر میرسد نسبت به اینکه بصورت جداگانه آنها را ثبت نماییم.
برنامه نویسان معمولا برای جلوگیری از ایجاد جدول واسطی [1] که رابطههای چند به چند زیادی دارد از یک لیست که با کاما دادههایش از هم جدا شدهاند، استفاده میکنند. بدین جهت اسم این بخش jaywalking ,antipatten میباشد، زیرا jaywalking نیز عملیاتی است که از تقاطع جلوگیری میکند.
1.1 هدف: ذخیره کردن چندین صفت
طراحی کردن جدولی که ستون آن فقط یک مقدار دارد، بسیار ساده و آسان میباشد. شما میتوانید نوع دادهایی که متعلق به ستون میباشد را انتخاب نمایید. مثلا از نوع (int,date…)
چگونه میتوانیم مجموعهایی از مقادیری که به یکدیگر مرتبط هستند را در یک ستون ذخیره نماییم ؟
در مثال ردیابی خطای پایگاه داده، ما یک محصول را به یک کاربر، با استفاده از ستونی که نوع آن integer است، مرتبط مینماییم. هر کاربر ممکن است محصولاتی داشته باشد و هر محصول به یک contact اشاره کند. بنابراین یک ارتباط چند به یک بین محصولات و کاربر برقرار است. برعکس این موضوع نیز صادق است؛ یعنی امکان دارد هر محصول متعلق به چندین کاربر باشد و یک ارتباط یک به چند ایجاد شود. در زیر جدول محصولات را به صورت عادی آورده شده است:
CREATE TABLE Products ( product_id SERIAL PRIMARY KEY, product_name VARCHAR(1000), account_id BIGINT UNSIGNED, -- . . . FOREIGN KEY (account_id) REFERENCES Accounts(account_id) ); INSERT INTO Products (product_id, product_name, account_id) VALUES (DEFAULT, 'Visual TurboBuilder' , 12);
CREATE TABLE Products ( product_id SERIAL PRIMARY KEY, product_name VARCHAR(1000), account_id VARCHAR(100), -- comma-separated list -- . . . ); INSERT INTO Products (product_id, product_name, account_id) VALUES (DEFAULT, 'Visual TurboBuilder' , '12,34' );
اکنون مشکلات کارایی و جامعیت دادهها را در این راه حل پیشنهادی بررسی مینماییم .
بدست آوردن محصولاتی برای یک کاربر خاص
بدلیل اینکه تمامی شناسهی کاربران که بصورت کلید خارجی جدول Products میباشند به صورت رشته در یک فیلد ذخیره شدهاند و حالت ایندکس بودن آنها از دست رفته است، بدست آوردن محصولاتی برای یک کاربر خاص سخت میباشد. به عنوان مثال بدست آوردن محصولاتی که کاربری با شناسهی 12 خریداری نموده بهصورت زیر میباشد:
SELECT * FROM Products WHERE account_id REGEXP '[[:<:]]12[[:>:]]' ;
SELECT * FROM Products AS p JOIN Accounts AS a ON p.account_id REGEXP '[[:<:]]' || a.account_id || '[[:>:]]' WHERE p.product_id = 123;
SELECT product_id, LENGTH(account_id) - LENGTH(REPLACE(account_id, ',' , '' )) + 1 AS contacts_per_product FROM Products;
UPDATE Products SET account_id = account_id || ',' || 56 WHERE product_id = 123;
<?php $stmt = $pdo->query( "SELECT account_id FROM Products WHERE product_id = 123"); $row = $stmt->fetch(); $contact_list = $row['account_id' ]; // change list in PHP code $value_to_remove = "34"; $contact_list = split(",", $contact_list); $key_to_remove = array_search($value_to_remove, $contact_list); unset($contact_list[$key_to_remove]); $contact_list = join(",", $contact_list); $stmt = $pdo->prepare( "UPDATE Products SET account_id = ? WHERE product_id = 123"); $stmt->execute(array($contact_list));
INSERT INTO Products (product_id, product_name, account_id) VALUES (DEFAULT, 'Visual TurboBuilder' , '12,34,banana' );
محدودیت طول لیست
UPDATE Products SET account_id = '10,14,18,22,26,30,34,38,42,46' WHERE product_id = 123;
UPDATE Products SET account_id = '101418,222630,343842,467790' WHERE product_id = 123;
CREATE TABLE Contacts ( product_id BIGINT UNSIGNED NOT NULL, account_id BIGINT UNSIGNED NOT NULL, PRIMARY KEY (product_id, account_id), FOREIGN KEY (product_id) REFERENCES Products(product_id), FOREIGN KEY (account_id) REFERENCES Accounts(account_id) );
جدول
Contacts یک جدول رابطه ایی بین جداول Products,Accounts
بدست آوردن محصولات برای کاربران و موارد مربوط به آن
ما براحتی میتوانیم تمامی محصولاتی که مختص به یک کاربر هستند را بدست آوریم. در این شیوه خاصیت ایندکس بودن شناسهی کاربران حفظ میشود به همین دلیل queryهای آن برای خواندن و بهینه کردن راحتتر میباشند. در این روش به کاراکتری برای جدا کردن ورودیها از یکدیگر نیاز نداریم چون هر کدام از آنها در یک سطر جداگانه ثبت میشوند. برای ویرایش کردن کاربرانی که یک محصول را خریداری نموده اند، کافیست یک سطر از جدول واسط را اضافه یا حذف نماییم. درنمونه کد زیر، ابتدا در جدول Contacts کاربری با شناسهی 34 که محصولی با شناسهی 456 را خریداری کرده، درج شده است و در خط بعد عملیات حذف با شرط آنکه شناسهی کاربر و محصول به ترتیب 34،456 باشد روی جدول Contacts اعمال شده است.
INSERT INTO Contacts (product_id, account_id) VALUES (456, 34); DELETE FROM Contacts WHERE product_id = 456 AND account_id = 34;
ایجاد توابع تجمیعی
به عنوان نمونه در مثال زیر براحتی ما میتوانیم تعداد محصولات در هر حساب کاربری را بدست آوریم:
SELECT account_id, COUNT(*) AS products_per_account FROM Contacts GROUP BY account_id;
اعتبارسنجی شناسه محصولات
از آنجاییکه مقادیری که در جدول قرار دارند کلید خارجی میباشند میتوان صحت اعتبار آنها را بررسی نمود. بعنوان مثال Contacts.account_id به Account.account_id اشاره میکند. در ضمن برای هر فیلد نوع آن را میتوان مشخص کرد تا فقط همان نوع داده را بپذیرد.
محدودیت طول لیست
نسبت به روش قبلی تقریبا در این حالت محدودیتی برای تعداد کاراکترهای ورودی نداریم.
مزیتهای دیگر استفاده از جدول واسط
کارایی روش دوم بهتر از حالت قبلی میباشد چون ایندکس بودن شناسهها حفظ شده است. همچنین براحتی میتوانیم فیلدی را به این جدول اضافه نماییم مثلا (time, date… )
export function error(){ alert('oops, an error'); }
var module = await JS.InvokeAsync<IJSObjectReference>("import", "./Panel.razor.js"); await module.InvokeVoidAsync("error");
Pages/Panel.razor Pages/Panel.razor.js Pages/Panel.razor.css
_module = await JS.InvokeAsync<IJSObjectReference>("import", "./_content/RazorClassLibrary/componentName.razor.js");
export function showPrompt(message) { return prompt(message, 'Type anything here'); }
@page "/call-js-example-6" @implements IAsyncDisposable @inject IJSRuntime JS <h1>Call JS Example 6</h1> <p> <button @onclick="TriggerPrompt">Trigger browser window prompt</button> </p> <p> @result </p> @code { private IJSObjectReference? module; private string? result; protected override async Task OnAfterRenderAsync(bool firstRender) { if (firstRender) { module = await JS.InvokeAsync<IJSObjectReference>("import", "./scripts.js"); } } private async Task TriggerPrompt() { result = await Prompt("Provide some text"); } public async ValueTask<string?> Prompt(string message) => module is not null ? await module.InvokeAsync<string>("showPrompt", message) : null; async ValueTask IAsyncDisposable.DisposeAsync() { if (module is not null) { await module.DisposeAsync(); } } }