public abstract class ResourceProviderFactory { public abstract IResourceProvider CreateGlobalResourceProvider(string classKey); public abstract IResourceProvider CreateLocalResourceProvider(string virtualPath); }
public interface IResourceProvider { IResourceReader ResourceReader { get; } object GetObject(string resourceKey, CultureInfo culture); }
// Type: System.Web.Compilation.ResXResourceProviderFactory // Assembly: System.Web, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a // Assembly location: C:\Windows\Microsoft.NET\assembly\GAC_32\System.Web\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Web.dll using System.Runtime; using System.Web; namespace System.Web.Compilation { internal class ResXResourceProviderFactory : ResourceProviderFactory { [TargetedPatchingOptOut("Performance critical to inline this type of method across NGen image boundaries")] public ResXResourceProviderFactory() { } public override IResourceProvider CreateGlobalResourceProvider(string classKey) { return (IResourceProvider) new GlobalResXResourceProvider(classKey); } public override IResourceProvider CreateLocalResourceProvider(string virtualPath) { return (IResourceProvider) new LocalResXResourceProvider(VirtualPath.Create(virtualPath)); } } }
internal class GlobalResXResourceProvider : BaseResXResourceProvider { private string _classKey; internal GlobalResXResourceProvider(string classKey) { _classKey = classKey; } protected override ResourceManager CreateResourceManager() { string fullClassName = BaseResourcesBuildProvider.DefaultResourcesNamespace + "." + _classKey; // If there is no app resource assembly, return null if (BuildManager.AppResourcesAssembly == null) return null; ResourceManager resourceManager = new ResourceManager(fullClassName, BuildManager.AppResourcesAssembly); resourceManager.IgnoreCase = true; return resourceManager; } public override IResourceReader ResourceReader { get { // App resources don't support implicit resources, so the IResourceReader should never be needed throw new NotSupportedException(); } } }
internal const string DefaultResourcesNamespace = "Resources";
internal class LocalResXResourceProvider : BaseResXResourceProvider { private VirtualPath _virtualPath; internal LocalResXResourceProvider(VirtualPath virtualPath) { _virtualPath = virtualPath; } protected override ResourceManager CreateResourceManager() { ResourceManager resourceManager = null; Assembly pageResAssembly = GetLocalResourceAssembly(); if (pageResAssembly != null) { string fileName = _virtualPath.FileName; resourceManager = new ResourceManager(fileName, pageResAssembly); resourceManager.IgnoreCase = true; } else { throw new InvalidOperationException(SR.GetString(SR.ResourceExpresionBuilder_PageResourceNotFound)); } return resourceManager; } public override IResourceReader ResourceReader { get { // Get the local resource assembly for this page Assembly pageResAssembly = GetLocalResourceAssembly(); if (pageResAssembly == null) return null; // Get the name of the embedded .resource file for this page string resourceFileName = _virtualPath.FileName + ".resources"; // Make it lower case, since GetManifestResourceStream is case sensitive resourceFileName = resourceFileName.ToLower(CultureInfo.InvariantCulture); // Get the resource stream from the resource assembly Stream resourceStream = pageResAssembly.GetManifestResourceStream(resourceFileName); // If this page has no resources, return null if (resourceStream == null) return null; return new ResourceReader(resourceStream); } } [PermissionSet(SecurityAction.Assert, Unrestricted = true)] private Assembly GetLocalResourceAssembly() { // Remove the page file name to get its directory VirtualPath virtualDir = _virtualPath.Parent; // Get the name of the local resource assembly string cacheKey = BuildManager.GetLocalResourcesAssemblyName(virtualDir); BuildResult result = BuildManager.GetBuildResultFromCache(cacheKey); if (result != null) { return ((BuildResultCompiledAssembly)result).ResultAssembly; } return null; } }
نکته: باتوجه به استفاده از عبارات بومیسازی ضمنی در استفاده از ورودیهای منابع محلی، خاصیت ResourceReader در این کلاس نمونهای متناظر برای درخواست جاری از کلاس ResourceReader با استفاده از Stream استخراج شده از اسمبلی یافته شده، تولید میکند.
internal abstract class BaseResXResourceProvider : IResourceProvider { private ResourceManager _resourceManager; ///// IResourceProvider implementation public virtual object GetObject(string resourceKey, CultureInfo culture) { // Attempt to get the resource manager EnsureResourceManager(); // If we couldn't get a resource manager, return null if (_resourceManager == null) return null; if (culture == null) culture = CultureInfo.CurrentUICulture; return _resourceManager.GetObject(resourceKey, culture); } public virtual IResourceReader ResourceReader { get { return null; } } ///// End of IResourceProvider implementation protected abstract ResourceManager CreateResourceManager(); private void EnsureResourceManager() { if (_resourceManager != null) return; _resourceManager = CreateResourceManager(); } }
System.Resources.ResourceManager(string baseName, Assembly assemblyName)
var manager = new System.Resources.ResourceManager("Resources.Resource1", typeof(Resource1).Assembly)
var manager = new System.Resources.ResourceManager("Resources.Resource1", Assembly.LoadFile(@"c:\MyResources\MyGlobalResources.dll"))
public static ResourceManager CreateFileBasedResourceManager(string baseName, string resourceDir, Type usingResourceSet)
resgen d:\MyResources\MyResource.fa.resx
private ResourceManager CreateGlobalResourceManager(string classKey) { var baseName = "Resources." + classKey; var buildManagerType = typeof(BuildManager); var property = buildManagerType.GetProperty("AppResourcesAssembly", BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.GetField); var appResourcesAssembly = (Assembly)property.GetValue(null, null); return new ResourceManager(baseName, appResourcesAssembly) { IgnoreCase = true }; }
var manager = CreateGlobalResourceManager("Resource1"); Label1.Text = manager.GetString("String1");
private ResourceManager CreateLocalResourceManager(string virtualPath) { var virtualPathType = typeof(BuildManager).Assembly.GetType("System.Web.VirtualPath", true); var virtualPathInstance = Activator.CreateInstance(virtualPathType, BindingFlags.NonPublic | BindingFlags.Instance, null, new object[] { virtualPath }, CultureInfo.InvariantCulture); var buildResultCompiledAssemblyType = typeof(BuildManager).Assembly.GetType("System.Web.Compilation.BuildResultCompiledAssembly", true); var propertyResultAssembly = buildResultCompiledAssemblyType.GetProperty("ResultAssembly", BindingFlags.NonPublic | BindingFlags.Instance); var methodGetLocalResourcesAssemblyName = typeof(BuildManager).GetMethod("GetLocalResourcesAssemblyName", BindingFlags.NonPublic | BindingFlags.Static); var methodGetBuildResultFromCache = typeof(BuildManager).GetMethod("GetBuildResultFromCache", BindingFlags.NonPublic | BindingFlags.Static, null, new Type[] { typeof(string) }, null); var fileNameProperty = virtualPathType.GetProperty("FileName"); var virtualPathFileName = (string)fileNameProperty.GetValue(virtualPathInstance, null); var parentProperty = virtualPathType.GetProperty("Parent"); var virtualPathParent = parentProperty.GetValue(virtualPathInstance, null); var localResourceAssemblyName = (string)methodGetLocalResourcesAssemblyName.Invoke(null, new object[] { virtualPathParent }); var buildResultFromCache = methodGetBuildResultFromCache.Invoke(null, new object[] { localResourceAssemblyName }); Assembly localResourceAssembly = null; if (buildResultFromCache != null) localResourceAssembly = (Assembly)propertyResultAssembly.GetValue(buildResultFromCache, null); if (localResourceAssembly == null) throw new InvalidOperationException("Unable to find the matching resource file."); return new ResourceManager(virtualPathFileName, localResourceAssembly) { IgnoreCase = true }; }
نحوه استفاده از متد فوق نیز به صورت زیر است:
var manager = CreateLocalResourceManager("~/Default.aspx"); Label1.Text = manager.GetString("Label1.Text");
@{ var token = await HttpContext.GetTokenAsync("access_token"); } <component type="typeof(App)" render-mode="ServerPrerendered" param-AccessToken="token" />
<CascadingValue Name="AccessToken" Value="AccessToken"> <CascadingAuthenticationState> <Router AppAssembly="@typeof(Program).Assembly"> <Found Context="routeData"> <AuthorizeRouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" /> </Found> <NotFound> <LayoutView Layout="@typeof(MainLayout)"> <p>Sorry, there's nothing at this address.</p> </LayoutView> </NotFound> </Router> </CascadingAuthenticationState> </CascadingValue> @code{ [Parameter] public string AccessToken { get; set; } }
@page "/showtoken" <p>This is part of the access token @(AccessToken != null ? AccessToken.Substring(0,30) : "(null)")</p> @code { [CascadingParameter(Name = "AccessToken")] public string AccessToken { get; set; } }
var userName = await HttpContext.User.Identity.Name;
var khasCookie = HttpContext.Request.Cookies["khas"];
جهت نگهداری بعضی از اطلاعات در صفحات کاربر، از فیلدهای مخفی ( Hidden Inputs ) استفاده میکنیم. مشکلی که در این روش وجود دارد این است که اگر این اطلاعات مهم باشند (مانند کلیدها) کاربر میتواند توسط ابزارهایی این اطلاعات را تغییر دهد و این مورد مسئلهای خطرناک میباشد.
راه حل رفع این مسئلهی امنیتی، استفاده از یک Html Helper جهت رمزنگاری این فیلد مخفی در مرورگر کاربر و رمز گشایی آن هنگام Post شدن سمت سرور میباشد.
برای رسیدن به این هدف یک Controller Factory ( Understanding and Extending Controller Factory in MVC ) سفارشی را جهت دستیابی به مقادیر فرم ارسالی، قبل از استفاده در Actionها و به همراه کلاسهای زیر ایجاد کردیم.
کلاس EncryptSettingsProvider :public interface IEncryptSettingsProvider { byte[] EncryptionKey { get; } string EncryptionPrefix { get; } } public class EncryptSettingsProvider : IEncryptSettingsProvider { private readonly string _encryptionPrefix; private readonly byte[] _encryptionKey; public EncryptSettingsProvider() { //read settings from configuration var useHashingString = ConfigurationManager.AppSettings["UseHashingForEncryption"]; var useHashing = System.String.Compare(useHashingString, "false", System.StringComparison.OrdinalIgnoreCase) != 0; _encryptionPrefix = ConfigurationManager.AppSettings["EncryptionPrefix"]; if (string.IsNullOrWhiteSpace(_encryptionPrefix)) { _encryptionPrefix = "encryptedHidden_"; } var key = ConfigurationManager.AppSettings["EncryptionKey"]; if (useHashing) { var hash = new SHA256Managed(); _encryptionKey = hash.ComputeHash(Encoding.UTF8.GetBytes(key)); hash.Clear(); hash.Dispose(); } else { _encryptionKey = Encoding.UTF8.GetBytes(key); } } #region ISettingsProvider Members public byte[] EncryptionKey { get { return _encryptionKey; } } public string EncryptionPrefix { get { return _encryptionPrefix; } } #endregion }
EncryptionKey : کلید رمز نگاری میباشد و در فایل Config برنامه ذخیره میباشد.
EncryptionPrefix : پیشوند نام Hidden فیلدها میباشد، این پیشوند برای یافتن Hidden فیلد هایی که رمزنگاری شده اند استفاده میشود. میتوان این فیلد را در فایل Config برنامه ذخیره کرد.
<appSettings> <add key="EncryptionKey" value="asdjahsdkhaksj dkashdkhak sdhkahsdkha kjsdhkasd"/> </appSettings>
کلاس RijndaelStringEncrypter :
public interface IRijndaelStringEncrypter : IDisposable { string Encrypt(string value); string Decrypt(string value); } public class RijndaelStringEncrypter : IRijndaelStringEncrypter { private RijndaelManaged _encryptionProvider; private ICryptoTransform _cryptoTransform; private readonly byte[] _key; private readonly byte[] _iv; public RijndaelStringEncrypter(IEncryptSettingsProvider settings, string key) { _encryptionProvider = new RijndaelManaged(); var keyBytes = Encoding.UTF8.GetBytes(key); var derivedbytes = new Rfc2898DeriveBytes(settings.EncryptionKey, keyBytes, 3); _key = derivedbytes.GetBytes(_encryptionProvider.KeySize / 8); _iv = derivedbytes.GetBytes(_encryptionProvider.BlockSize / 8); } #region IEncryptString Members public string Encrypt(string value) { var valueBytes = Encoding.UTF8.GetBytes(value); if (_cryptoTransform == null) { _cryptoTransform = _encryptionProvider.CreateEncryptor(_key, _iv); } var encryptedBytes = _cryptoTransform.TransformFinalBlock(valueBytes, 0, valueBytes.Length); var encrypted = Convert.ToBase64String(encryptedBytes); return encrypted; } public string Decrypt(string value) { var valueBytes = Convert.FromBase64String(value); if (_cryptoTransform == null) { _cryptoTransform = _encryptionProvider.CreateDecryptor(_key, _iv); } var decryptedBytes = _cryptoTransform.TransformFinalBlock(valueBytes, 0, valueBytes.Length); var decrypted = Encoding.UTF8.GetString(decryptedBytes); return decrypted; } #endregion #region IDisposable Members public void Dispose() { if (_cryptoTransform != null) { _cryptoTransform.Dispose(); _cryptoTransform = null; } if (_encryptionProvider != null) { _encryptionProvider.Clear(); _encryptionProvider.Dispose(); _encryptionProvider = null; } } #endregion }
Rijndael :Represents the base class from which all implementations of the Rijndael symmetric encryption algorithm must inherit
متغیر key در سازنده کلاس کلیدی جهت رمزنگاری و رمزگشایی میباشد. این کلید میتواند AntiForgeryToken تولیدی در View ها و یا کلیدی باشد که در سیستم خودمان ذخیره سازی میکنیم.
در این پروژه از کلید سیستم خودمان استفاده میکنیم.
کلاس ActionKey :
public class ActionKey { public string Area { get; set; } public string Controller { get; set; } public string Action { get; set; } public string ActionKeyValue { get; set; } }
در اینجا هر View که بخواهد از این فیلد رمزنگاری شده استفاده کند بایستی دارای کلیدی در سیستم باشد.مدل متناظر مورد استفاده را مشاهده مینمایید. در این مدل، ActionKeyValue کلیدی جهت رمزنگاری این فیلد مخفی میباشد.
کلاس ActionKeyService :
/// <summary> /// پیدا کردن کلید متناظر هر ویو.ایجاد کلید جدید در صورت عدم وجود کلید در سیستم /// </summary> /// <param name="action"></param> /// <param name="controller"></param> /// <param name="area"></param> /// <returns></returns> string GetActionKey(string action, string controller, string area = ""); } public class ActionKeyService : IActionKeyService { private static readonly IList<ActionKey> ActionKeys; static ActionKeyService() { ActionKeys = new List<ActionKey> { new ActionKey { Area = "", Controller = "Product", Action = "dit", ActionKeyValue = "E702E4C2-A3B9-446A-912F-8DAC6B0444BC", } }; } /// <summary> /// پیدا کردن کلید متناظر هر ویو.ایجاد کلید جدید در صورت عدم وجود کلید در سیستم /// </summary> /// <param name="action"></param> /// <param name="controller"></param> /// <param name="area"></param> /// <returns></returns> public string GetActionKey(string action, string controller, string area = "") { area = area ?? ""; var actionKey= ActionKeys.FirstOrDefault(a => a.Action.ToLower() == action.ToLower() && a.Controller.ToLower() == controller.ToLower() && a.Area.ToLower() == area.ToLower()); return actionKey != null ? actionKey.ActionKeyValue : AddActionKey(action, controller, area); } /// <summary> /// اضافه کردن کلید جدید به سیستم /// </summary> /// <param name="action"></param> /// <param name="controller"></param> /// <param name="area"></param> /// <returns></returns> private string AddActionKey(string action, string controller, string area = "") { var actionKey = new ActionKey { Action = action, Controller = controller, Area = area, ActionKeyValue = Guid.NewGuid().ToString() }; ActionKeys.Add(actionKey); return actionKey.ActionKeyValue; } }
جهت بازیابی کلید هر View میباشد. در متد GetActionKey ابتدا بدنبال کلید View درخواستی در منبعی از ActionKeyها میگردیم. اگر این کلید یافت نشد کلیدی برای آن ایجاد میکنیم و نیازی به مقدار دهی آن نمیباشد.
کلاس MvcHtmlHelperExtentions :
public static class MvcHtmlHelperExtentions { public static string GetActionKey(this System.Web.Routing.RequestContext requestContext) { IActionKeyService actionKeyService = new ActionKeyService(); var action = requestContext.RouteData.Values["Action"].ToString(); var controller = requestContext.RouteData.Values["Controller"].ToString(); var area = requestContext.RouteData.Values["Area"]; var actionKeyValue = actionKeyService.GetActionKey( action, controller, area != null ? area.ToString() : null); return actionKeyValue; } public static string GetActionKey(this HtmlHelper helper) { IActionKeyService actionKeyService = new ActionKeyService(); var action = helper.ViewContext.RouteData.Values["Action"].ToString(); var controller = helper.ViewContext.RouteData.Values["Controller"].ToString(); var area = helper.ViewContext.RouteData.Values["Area"]; var actionKeyValue = actionKeyService.GetActionKey( action, controller, area != null ? area.ToString() : null); return actionKeyValue; } }
public static string GetActionKey(this System.Web.Routing.RequestContext requestContext)
public static string GetActionKey(this HtmlHelper helper)
کلاس InputExtensions :
public static class InputExtensions { public static MvcHtmlString EncryptedHidden(this HtmlHelper helper, string name, object value) { if (value == null) { value = string.Empty; } var strValue = value.ToString(); IEncryptSettingsProvider settings = new EncryptSettingsProvider(); var encrypter = new RijndaelStringEncrypter(settings, helper.GetActionKey()); var encryptedValue = encrypter.Encrypt(strValue); encrypter.Dispose(); var encodedValue = helper.Encode(encryptedValue); var newName = string.Concat(settings.EncryptionPrefix, name); return helper.Hidden(newName, encodedValue); } public static MvcHtmlString EncryptedHiddenFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression) { var name = ExpressionHelper.GetExpressionText(expression); var metadata = ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData); return EncryptedHidden(htmlHelper, name, metadata.Model); } }
@Html.EncryptedHiddenFor(model => model.Id) @Html.EncryptedHidden("Id2","2")
public class DecryptingControllerFactory : DefaultControllerFactory { private readonly IEncryptSettingsProvider _settings; public DecryptingControllerFactory() { _settings = new EncryptSettingsProvider(); } public override IController CreateController(System.Web.Routing.RequestContext requestContext, string controllerName) { var parameters = requestContext.HttpContext.Request.Params; var encryptedParamKeys = parameters.AllKeys.Where(x => x.StartsWith(_settings.EncryptionPrefix)).ToList(); IRijndaelStringEncrypter decrypter = null; foreach (var key in encryptedParamKeys) { if (decrypter == null) { decrypter = GetDecrypter(requestContext); } var oldKey = key.Replace(_settings.EncryptionPrefix, string.Empty); var oldValue = decrypter.Decrypt(parameters[key]); if (requestContext.RouteData.Values[oldKey] != null) { if (requestContext.RouteData.Values[oldKey].ToString() != oldValue) throw new ApplicationException("Form values is modified!"); } requestContext.RouteData.Values[oldKey] = oldValue; } if (decrypter != null) { decrypter.Dispose(); } return base.CreateController(requestContext, controllerName); } private IRijndaelStringEncrypter GetDecrypter(System.Web.Routing.RequestContext requestContext) { var decrypter = new RijndaelStringEncrypter(_settings, requestContext.GetActionKey()); return decrypter; } }
این قسمت از کد
if (requestContext.RouteData.Values[oldKey] != null) { if (requestContext.RouteData.Values[oldKey].ToString() != oldValue) throw new ApplicationException("Form values is modified!"); }
همچنین بایستی این Controller Factory را در Application_Start فایل global.asax.cs برنامه اضافه نماییم.
protected void Application_Start() { .... ControllerBuilder.Current.SetControllerFactory(typeof(DecryptingControllerFactory)); }
کدهای پروژهی جاری
TestHiddenEncrypt.7z
*در تکمیل این مقاله میتوان SessionId کاربر یا AntyForgeryToken تولیدی در View را نیز در کلید دخالت داد و در هربار Post شدن اطلاعات این ActionKeyValue مربوط به کاربر جاری را تغییر داد و کلیدها را در بانکهای اطلاعاتی ذخیره نمود.
مراجع:
Automatic Encryption of Secure Form Field Data
Encrypted Hidden Redux : Let's Get Salty
EF Code First #2
- در کل رویه ذخیره شدهی سیستمی به نام SP_WHO وجود دارد که مصرف کنندگان را لیست میکند. شماره آنها را یافته و سپس توسط رویه ذخیره شده دیگری به نام Kill، حذفشان کنید.
- روش دیگر drop آنی یک بانک اطلاعاتی، تک کاربره کردن و سپس حذف آن است:
alter database [MyDatbase] set single_user with rollback immediate drop database [MyDatabase]
Url Routing در ASP.Net WebForms
این دوجمله رو
«- داده هایی که اخیرا به DbContext اضافه شدهاند ولی هنوز در Database ذخیره نشدهاند، درنظر گرفته نخواهند شد.
- داده هایی که در DbContext حذف شدهاند ولی در Database هستند، در نتیجه پرس و جو خواهند بود.»
میشه خلاصهاش کرد به «تا زمانیکه SaveChanges فراخوانی نشه، از اطلاعات تغییر کرده نمیشه کوئری گرفت (کوئریها همیشه روی دیتابیس انجام میشن)؛ اما خاصیت Local این تغییرات محلی رو داره یا اینکه در change tracker میشه موارد EntityState.Added | EntityState.Modified | EntityState.Unchanged رو هم کوئری گرفت».
ابزارهای پیش نیاز:
در اولین قدم، برنامهی متن باز Visual Studio Code را از اینجا دانلود و نصب کنید. برنامهی Visual Studio Code که در ادامهی فعالیتهای جدید متن باز مایکروسافت به بازار عرضه شده است، سریع، سبک و کاملا قابل توسعه و سفارشی سازی است و از اکثر زبانهای معروف پشتیبانی میکند.
در قدم بعدی، شما باید NET Core. را از اینجا (64 بیتی) دانلود و نصب کنید.
.NET Core چیست؟
NET Core. در واقع پیاده سازی بخشی از NET. اصلی است که به صورت متن باز در حال توسعه میباشد و بر روی لینوکس و مکینتاش هم قابل اجراست. موتور اجرای دات نت کامل CLR نام دارد و NET Core. نیز دارای موتور اجرایی CoreCLR است و شامل فریمورک CoreFX میباشد.
در حال حاضر شما میتوانید با استفاده از NET Core. برنامههای کنسولی و تحت وب با ASP.NET 5 بنویسید و احتمالا در آینده میتوان امیدوار بود که از ساختارهای پیچیدهتری مثل WPF نیز پشتیبانی کند.
پس از آنکه NET Core. را دانلود و نصب کردید، جهت شروع پروژه، یک پوشه را در یکی از درایوها ساخته (در این مثال E:\Projects\EF7-SQLite-NETCore) و Command prompt را در آنجا باز کنید. سپس دستورات زیر را به ترتیب اجرا کنید:
dotnet restore
dotnet run
- NuGet.Config (این فایل، تنظیمات مربوط به نیوگت را جهت کشف و دریافت وابستگیهای پروژه، شامل میشود)
- Program.cs (این فایل سی شارپ حاوی کد برنامه است)
- project.json (این فایل حاوی اطلاعات پلتفرم هدف و لیست وابستگیهای پروژه است)
دستور dotnet restore بر اساس لیست وابستگیها و پلتفرم هدف، وابستگیهای لازم را از مخزن نیوگت دریافت میکند. (در صورتی که در هنگام اجرای این دستور با خطای NullReferenceException مواجه شدید از دستور dnu restore استفاده کنید. این خطا در گیت هاب در حال بررسی است)
دستور dotnet run هم سورس برنامه را کامپایل و اجرا میکند. در صورتی که پیام Hello World را مشاهده کردید، یعنی برنامهی شما تحت NET Core. با موفقیت اجرا شده است.
توسعهی پروژه با Visual Studio Code
در ادامه، قصد داریم پروژهی HelloWorld را تحت Visual Studio Code باز کرده و تغییرات بعدی را در آنجا اعمال کنیم. پس از باز کردن Visual Studio Code از منوی File گزینهی Open Folder را انتخاب کنید و پوشهی حاوی پروژه (EF7-SQLite-NETCore) را انتخاب کنید. اکنون پروژهی شما تحت VS Code باز شده و قابل ویرایش است.
سپس از لیست فایلهای پروژه، فایل project.json را باز کرده و در بخش "dependencies" یک ردیف را برای EntityFramework.SQLite به صورت زیر اضافه کنید. به محض افزودن این خط در project.json و ذخیرهی آن، در صورتیکه قبلا این وابستگی دریافت نشده باشد، Visual Studio Code با نمایش یک هشدار در بالای برنامه به شما امکان دریافت اتوماتیک این وابستگی را میدهد. در نتیجه کافیست دکمهی Restore را زده و منتظر شوید تا وابستگی EntityFramework.SQLite از مخزن ناگت دانلود و برای پروژهی شما تنظیم شود.
"EntityFramework.SQLite": "7.0.0-rc1-final"
پس از کامل شدن این مرحله، در پروژههای بعدی تمام ارجاعات به وابستگیهای دریافت شده، از طریق مخزن موجود در سیستم خود شما، برطرف خواهد شد و نیاز به دانلود مجدد وابستگیها نیست.
اکنون همهی موارد، جهت توسعهی پروژه آماده است. ماوس خود را بر روی ریشهی پروژه در VS Code قرار داده و New Folder را انتخاب کنید و نام Models را برای آن تایپ کنید. این پوشه قرار است مدل کلاسهای پروژه را شامل شود. در اینجا ما یک مدل به نام Book داریم و نام کانتکست اصلی پروژه را هم LibraryContext گذاشتهایم.
بر روی پوشهی Models راست کلیک کرده و گزینهی New File را انتخاب کنید. سپس فایلهای Book.cs و LibraryContext.cs را ایجاد کرده و کدهای زیر را برای مدل و کانتکست، در درون این دو فایل قرار دهید.
Book.cs
namespace Models { public class Book { public int ID { get; set; } public string Title { get; set; } public string Author{get;set;} public int PublishYear { get; set; } } }
using Microsoft.Data.Entity; using Microsoft.Data.Sqlite; namespace Models { public class LibraryContext : DbContext { protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { var connectionStringBuilder = new SqliteConnectionStringBuilder { DataSource = "test.db" }; var connectionString = connectionStringBuilder.ToString(); var connection = new SqliteConnection(connectionString); optionsBuilder.UseSqlite(connection); } public DbSet<Book> Books { get; set; } } }
در قدم آخر هم کافیست که فایل Program.cs را تغییر دهید و مقادیری را در دیتابیس ذخیره و بازخوانی کنید.
using System; using Models; namespace ConsoleApplication { public class Program { public static void Main(string[] args) { Console.WriteLine("EF7 + Sqlite with the taste of .NET Core"); try { using (var context = new LibraryContext()) { context.Database.EnsureCreated(); var book1 = new Book() { Title = "Adaptive Code via C#: Agile coding with design patterns and SOLID principles ", Author = "Gary McLean Hall", PublishYear = 2014 }; var book2 = new Book() { Title = "CLR via C# (4th Edition)", Author = "Jefrey Ritcher", PublishYear = 2012 }; context.Books.Add(book1); context.Books.Add(book2); context.SaveChanges(); ReadData(context); } Console.WriteLine("Press any key to exit ..."); Console.ReadKey(); } catch (Exception ex) { Console.WriteLine($"An exception occured: {ex.Message}\n{ex.StackTrace}"); } } private static void ReadData(LibraryContext context) { Console.WriteLine("Books in database:"); foreach (var b in context.Books) { Console.WriteLine($"Book {b.ID}"); Console.WriteLine($"\tName: {b.Title}"); Console.WriteLine($"\tAuthor: {b.Author}"); Console.WriteLine($"\tPublish Year: {b.PublishYear}"); Console.WriteLine(); } } } }
جهت اجرای برنامه کافیست Command prompt را در آدرس پروژه باز کرده و دستور dotnet run را اجرا کنید. پروژهی شما کامپایل و اجرا میشود و خروجی مشابه زیر را مشاهده خواهید کرد. اگر برنامه را مجددا اجرا کنید، به جای دو کتاب اطلاعات چهار کتاب نمایش داده خواهد شد؛ چرا که در هر مرحله اطلاعات دو کتاب در دیتابیس درج میشود.
اگر به پوشهی bin که در پوشهی پروژه ایجاد شده است، نگاهی بیندازید، خبری از فایل باینری نیست. چرا که در لحظه، تولید و اجرا شده است. جهت build کردن پروژه و تولید فایل باینری کافیست دستور dotnet build را اجرا کنید، تا فایل باینری در پوشهی bin ایجاد شود.
جهت انتشار برنامه میتوانید دستور dotnet publish را اجرا کنید. این دستور نه تنها برنامه، که تمام وابستگیهای مورد نیاز آن را برای اجرای در یک پلتفرم خاص تولید میکند. برای مثال بعد از اجرای این دستور یک پوشهی win7-x64 حاوی 211 فایل در مجموع تولید شده است که تمامی وابستگیهای این پروژه را شامل میشود.
در واقع این پوشه تمام وابستگیهای مورد نیاز پروژه را همراه خود دارد و در نتیجه جهت اجرای این برنامه برخلاف برنامههای معمولی دات نت، دیگر نیازی به نصب هیچ وابستگی مجزایی نیست و حتی پروژههای نوشته شده تحت NET Core. را میتوانید در سیستمهای عاملهای دیگری مثل لینوکس و مکینتاش و یا Windows IoT بر روی سخت افزار Raspberry Pi 2 هم اجرا کنید.
جهت مطالعهی بیشتر: