सॉफ्टवेयर सेटिंग्स प्रबंधन को लागू करना शायद उन चीजों में से एक है जो लगभग हर एप्लिकेशन में अलग-अलग तरीके से लागू किए जाते हैं। अधिकांश फ्रेमवर्क और अन्य ऐड-ऑन आमतौर पर पैरामीटर स्टोरेज के कुछ कुंजी-मूल्य से मूल्यों को बचाने / लोड करने के लिए अपने स्वयं के उपकरण प्रदान करते हैं।
हालांकि, ज्यादातर मामलों में, एक विशिष्ट सेटिंग विंडो के कार्यान्वयन और संबंधित कई चीजें उपयोगकर्ता के विवेक पर छोड़ दी जाती हैं। इस लेख में मैं उस दृष्टिकोण को साझा करना चाहता हूं जिसे मैं करने में कामयाब रहा। मेरे मामले में, मुझे एमवीवीएम-अनुकूल शैली में सेटिंग्स के साथ काम को लागू करने की आवश्यकता है और इस मामले में उपयोग किए गए कैटल फ्रेम की बारीकियों का उपयोग कर।
डिस्क्लेमर : इस नोट में बुनियादी प्रतिबिंब की तुलना में अधिक कठिन कोई तकनीकी सूक्ष्मता नहीं होगी। यह एक छोटी सी समस्या को हल करने के दृष्टिकोण का वर्णन है जो मुझे सप्ताहांत में मिला है। मैं इस बात पर विचार करना चाहता था कि मानक बायलरप्लेट कोड और कॉपी-पेस्ट से संबंधित बचत / लोडिंग सेटिंग से कैसे छुटकारा पाया जाए। उपलब्ध समाधान केवल सुविधाजनक .NET / कैटल टूल्स के लिए बल्कि तुच्छ धन्यवाद के रूप में निकला, लेकिन हो सकता है कि कोई व्यक्ति कुछ घंटों का समय बचाएगा या उपयोगी विचार सुझाएगा।
कैटल फ्रेमवर्क संक्षिप्तअन्य WPF फ्रेमवर्क (प्रिज्म, एमवीवीएम लाइट, कैलीबर्न.माइक्रो, आदि) की तरह, कैटेल एमवीवीएम शैली में अनुप्रयोगों के निर्माण के लिए सुविधाजनक उपकरण प्रदान करता है।
मुख्य घटक:
- IoC (MVVM घटकों के साथ एकीकृत)
- मॉडलबेस: एक बेस क्लास जो प्रॉपर्टीचेंज्ड (विशेष रूप से कैटेल.फीडि के साथ संयोजन के रूप में), क्रमबद्धता और बिग्रेडिट / कैंसिल्ड / एंडएडिट (क्लासिक "लागू करें" / "रद्द करें") का स्वत: कार्यान्वयन प्रदान करता है।
- ViewModelBase, इसके गुणों को लपेटते हुए, मॉडल को बांधने में सक्षम।
- विचारों के साथ काम करें, जो स्वचालित रूप से ViewModel को बना और बांध सकता है। नेस्टेड नियंत्रण समर्थित हैं।
आवश्यकताओं
हम इस तथ्य से आगे बढ़ेंगे कि हम विन्यास उपकरणों से निम्नलिखित चाहते हैं:
- सरल संरचित तरीके से कॉन्फ़िगरेशन तक पहुंच। उदाहरण के लिए
CultureInfo culture = settings.Application.PreferredCulture;
TimeSpan updateRate = settings.Perfomance.UpdateRate;
।
- सभी मापदंडों को सामान्य गुणों के रूप में प्रस्तुत किया जाता है। स्टोरेज मेथड अंदर से एनकैप्सुलेटेड है। सरल प्रकारों के लिए, सब कुछ स्वचालित रूप से होना चाहिए अधिक जटिल प्रकारों के लिए, मूल्य के क्रमांकन को एक स्ट्रिंग में कॉन्फ़िगर करना संभव होना चाहिए।
- सादगी और विश्वसनीयता। मैं संपूर्ण कॉन्फ़िगरेशन मॉडल को पूरी तरह से या कुछ एंटिटी फ्रेमवर्क को क्रमबद्ध करने जैसे नाजुक टूल का उपयोग नहीं करना चाहता। निचले स्तर पर, कॉन्फ़िगरेशन पैरामीटर-मान जोड़े का एक सरल भंडार रहता है।
- कॉन्फ़िगरेशन में किए गए परिवर्तनों को छोड़ने की क्षमता, उदाहरण के लिए, यदि उपयोगकर्ता ने सेटिंग्स विंडो में "रद्द" पर क्लिक किया।
- कॉन्फ़िगरेशन अपडेट की सदस्यता लेने की क्षमता। उदाहरण के लिए, हम कॉन्फ़िगरेशन बदलने के तुरंत बाद एप्लिकेशन भाषा को अपडेट करना चाहते हैं।
- अनुप्रयोग संस्करणों के बीच स्थानांतरण। एप्लिकेशन संस्करणों (नाम का पैरामीटर, आदि) के बीच स्विच करते समय कार्यों को सेट करना संभव होना चाहिए।
- न्यूनतम बॉयलरप्लेट कोड, न्यूनतम टाइपो। आदर्श रूप से, हम केवल ऑटो संपत्ति सेट करना चाहते हैं और यह नहीं सोचते हैं कि यह कैसे बचाया जाएगा, जिसके तहत स्ट्रिंग कुंजी, आदि ... हम सेटिंग्स विंडो के व्यू-मॉडल में प्रत्येक गुण को मैन्युअल रूप से कॉपी नहीं करना चाहते हैं, सब कुछ स्वचालित रूप से काम करना चाहिए।
मानक उपकरण
कैटेल IConfigurationService सेवा प्रदान करता है, जो स्थानीय भंडारण (मानक कार्यान्वयन में डिस्क पर एक फ़ाइल) से स्ट्रिंग कुंजी के भंडारण और लोडिंग मूल्यों की अनुमति देता है।
अगर हम इस सेवा का उपयोग अपने शुद्ध रूप में करना चाहते हैं, तो हमें इन चाबियों को स्वयं घोषित करना होगा, उदाहरण के लिए, ऐसे स्थिरांक स्थापित करके:
public static class Application { public const String PreferredCulture = "Application.PreferredCulture"; public static readonly String PreferredCultureDefaultValue = Thread.CurrentThread.CurrentUICulture.ToString(); }
फिर हम इन मापदंडों को इस तरह प्राप्त कर सकते हैं:
var preferredCulture = new CultureInfo(configurationService.GetRoamingValue( Application.PreferredCulture, Application.PreferredCultureDefaultValue));
यह लिखने के लिए बहुत कुछ और थकाऊ है, बहुत सी सेटिंग्स होने पर टाइपो बनाना आसान है। इसके अलावा, सेवा केवल सरल प्रकारों का समर्थन करती है, उदाहरण के लिए CultureInfo
को अतिरिक्त परिवर्तनों के बिना बचाया नहीं जा सकता है।
इस सेवा के साथ काम को सरल बनाने के लिए, कई घटकों से युक्त एक आवरण प्राप्त किया गया था।
पूरा नमूना कोड GitHub रिपॉजिटरी में उपलब्ध है । इसमें सेटिंग्स में कुछ मापदंडों को संपादित करने की क्षमता के साथ सबसे सरल अनुप्रयोग है और यह सुनिश्चित करें कि सब कुछ काम करता है। मैं स्थानीयकरण से परेशान नहीं हूं, सेटिंग्स में "भाषा" पैरामीटर का उपयोग केवल कॉन्फ़िगरेशन को प्रदर्शित करने के लिए किया जाता है। यदि दिलचस्पी है, तो कैटल के पास WPF स्तर पर सुविधाजनक स्थानीयकरण तंत्र हैं । यदि आप संसाधन फ़ाइलों की तरह नहीं हैं, तो आप अपने स्वयं के कार्यान्वयन को जीएनयू गेटटेक्स्ट के साथ काम कर सकते हैं, उदाहरण के लिए।
पठनीयता के लिए, इस प्रकाशन के पाठ में कोड उदाहरणों में, सभी xml-doc टिप्पणियों को हटा दिया गया है।
कॉन्फ़िगरेशन सेवा
एक ऐसी सेवा जिसे IoC के माध्यम से एम्बेड किया जा सकता है और आवेदन में कहीं से भी सेटिंग्स के साथ काम करने की सुविधा है।
सेवा का मुख्य उद्देश्य एक सेटिंग मॉडल प्रदान करना है, जो बदले में उन्हें एक्सेस करने का एक सरल और संरचित तरीका प्रदान करता है।
सेटिंग्स मॉडल के अलावा, सेवा सेटिंग्स में किए गए परिवर्तनों को पूर्ववत या सहेजने की क्षमता भी प्रदान करती है।
इंटरफेस:
public interface IApplicationConfigurationProviderService { event TypedEventHandler<IApplicationConfigurationProviderService> ConfigurationSaved; ConfigurationModel Configuration { get; } void LoadSettingsFromStorage(); void SaveChanges(); }
कार्यान्वयन:
public partial class ApplicationConfigurationProviderService : IApplicationConfigurationProviderService { private readonly IConfigurationService _configurationService; public ApplicationConfigurationProviderService(IConfigurationService configurationService) { _configurationService = configurationService; Configuration = new ConfigurationModel(); LoadSettingsFromStorage(); ApplyMigrations(); } public event TypedEventHandler<IApplicationConfigurationProviderService> ConfigurationSaved; public ConfigurationModel Configuration { get; } public void LoadSettingsFromStorage() { Configuration.LoadFromStorage(_configurationService); } public void SaveChanges() { Configuration.SaveToStorage(_configurationService); ConfigurationSaved?.Invoke(this); } private void ApplyMigrations() { var currentVersion = typeof(ApplicationConfigurationProviderService).Assembly.GetName().Version; String currentVersionString = currentVersion.ToString(); String storedVersionString = _configurationService.GetRoamingValue("SolutionVersion", currentVersionString); if (storedVersionString == currentVersionString) return;
कार्यान्वयन तुच्छ है, ConfigurationModel
की सामग्री निम्न अनुभागों में वर्णित है। केवल एक चीज जो ध्यान आकर्षित करने की संभावना है, वह है ApplyMigrations
विधि।
कार्यक्रम के नए संस्करण में, कुछ बदल सकता है, उदाहरण के लिए, कुछ जटिल पैरामीटर या इसके नाम को संग्रहीत करने की विधि। यदि हम मौजूदा मापदंडों को बदलने वाले प्रत्येक अपडेट के बाद अपनी सेटिंग्स को खोना नहीं चाहते हैं, तो हमें एक माइग्रेशन तंत्र की आवश्यकता है। ApplyMigrations
विधि संस्करणों के बीच संक्रमण के दौरान किसी भी कार्य को करने के लिए बहुत सरल समर्थन ApplyMigrations
।
यदि एप्लिकेशन के नए संस्करण में कुछ बदल गया है, तो हम पड़ोसी संस्करण में निहित माइग्रेशन की सूची में नए संस्करण में केवल आवश्यक क्रियाओं (उदाहरण के लिए, नए नाम के साथ पैरामीटर को सहेजना) को जोड़ते हैं:
private readonly IReadOnlyCollection<Migration> _migrations = new Migration[] { new Migration(new Version(1,1,0), () => {
सेटिंग्स मॉडल
नियमित संचालन का स्वचालन निम्नानुसार है। कॉन्फ़िगरेशन को एक नियमित मॉडल (डेटा-ऑब्जेक्ट) के रूप में वर्णित किया गया है। कैटल एक सुविधाजनक आधार वर्ग मॉडलबेस प्रदान करता है, जो उसके सभी MVVM उपकरणों का मूल है, जैसे कि सभी तीन MVVM घटकों के बीच स्वचालित बाइंडिंग। विशेष रूप से, यह आपको उन मॉडल संपत्तियों को आसानी से एक्सेस करने की अनुमति देता है जिन्हें हम सहेजना चाहते हैं।
इस तरह के एक मॉडल की घोषणा करके, हम इसके गुण प्राप्त कर सकते हैं, स्ट्रिंग कीज को मैप कर सकते हैं, उन्हें संपत्ति के नाम से बना सकते हैं, और फिर स्वचालित रूप से कॉन्फ़िगरेशन से मानों को लोड और सहेज सकते हैं। दूसरे शब्दों में, एक कॉन्फ़िगरेशन में गुणों और मूल्यों को बांधें।
कॉन्फ़िगरेशन विकल्पों की घोषणा
यह मूल मॉडल है:
public partial class ConfigurationModel : ConfigurationGroupBase { public ConfigurationModel() { Application = new ApplicationConfiguration(); Performance = new PerformanceConfiguration(); } public ApplicationConfiguration Application { get; private set; } public PerformanceConfiguration Performance { get; private set; } }
ApplicationConfiguration
और PerfomanceConfiguration
उनकी सेटिंग समूह का वर्णन करने वाले उपवर्ग हैं:
public partial class ConfigurationModel { public class PerformanceConfiguration : ConfigurationGroupBase { [DefaultValue(10)] public Int32 MaxUpdatesPerSecond { get; set; } } }
हुड के तहत, यह संपत्ति "Performance.MaxUpdatesPerSecond"
पैरामीटर से बंधेगी, जिसका नाम PerformanceConfiguration
के प्रकार से उत्पन्न होता है।
यह ध्यान दिया जाना चाहिए कि इन गुणों को घोषित करने की क्षमता कैटेलफोडी के उपयोग के लिए बहुत संक्षिप्त है। प्रसिद्ध .NET कोड जनरेटर फोडी के लिए एक प्लगइन। यदि किसी कारण से आप इसका उपयोग नहीं करना चाहते हैं, तो दस्तावेजों के अनुसार गुणों को सामान्य रूप से घोषित किया जाना चाहिए (डब्ल्यूपीएफ से डिपेंडेंसीप्रोपरेटी के समान)।
यदि वांछित है, तो घोंसले के शिकार के स्तर को बढ़ाया जा सकता है।
IConfigurationService के साथ प्रॉपर्टी बाइंडिंग लागू करें
आधार क्लास ConfigurationGroupBase
में बाइंडिंग होती है, जो बदले में मॉडलबेस से विरासत में मिली है। इसकी सामग्री पर अधिक विस्तार से विचार करें।
सबसे पहले, हम उन संपत्तियों की एक सूची बनाते हैं जिन्हें हम सहेजना चाहते हैं:
public abstract class ConfigurationGroupBase : ModelBase { private readonly IReadOnlyCollection<ConfigurationProperty> _configurationProperties; private readonly IReadOnlyCollection<PropertyData> _nestedConfigurationGroups; protected ConfigurationGroupBase() { var properties = this.GetDependencyResolver() .Resolve<PropertyDataManager>() .GetCatelTypeInfo(GetType()) .GetCatelProperties() .Select(property => property.Value) .Where(property => property.IncludeInBackup && !property.IsModelBaseProperty) .ToArray(); _configurationProperties = properties .Where(property => !property.Type.IsSubclassOf(typeof(ConfigurationGroupBase))) .Select(property => {
यहां, हम केवल कैटल मॉडल के लिए प्रतिबिंब के एनालॉग की ओर मुड़ते हैं, गुण प्राप्त करते हैं (उपयोगिता को फ़िल्टर करके या जिन्हें हमने स्पष्ट रूप से [ExcludeFromBackup]
विशेषता के साथ चिह्नित किया है) और उनके लिए स्ट्रिंग कुंजी उत्पन्न करते हैं। गुण जो स्वयं प्रकार के ConfigurationGroupBase
एक अलग सूची में सूचीबद्ध ConfigurationGroupBase
।
LoadFromStorage()
विधि कॉन्फ़िगरेशन से मानों को पहले प्राप्त गुणों या मानक मानों को लिखती है, यदि वे पहले सहेजे नहीं गए थे। उपसमूहों के लिए, उनके LoadFromStorage()
कहा जाता है:
public void LoadFromStorage(IConfigurationService configurationService) { foreach (var property in _configurationProperties) { try { LoadPropertyFromStorage(configurationService, property.ConfigurationKey, property.PropertyData); } catch (Exception ex) { Log.Error(ex, "Can't load from storage nested configuration group {Name}", property.PropertyData.Name); } } foreach (var property in _nestedConfigurationGroups) { var configurationGroup = GetValue(property) as ConfigurationGroupBase; if (configurationGroup == null) { Log.Error("Can't load from storage configuration property {Name}", property.Name); continue; } configurationGroup.LoadFromStorage(configurationService); } } protected virtual void LoadPropertyFromStorage(IConfigurationService configurationService, String configurationKey, PropertyData propertyData) { var objectConverterService = this.GetDependencyResolver().Resolve<IObjectConverterService>(); Object value = configurationService.GetRoamingValue(configurationKey, propertyData.GetDefaultValue()); if (value is String stringValue) value = objectConverterService.ConvertFromStringToObject(stringValue, propertyData.Type, CultureInfo.InvariantCulture); SetValue(propertyData, value); }
LoadPropertyFromStorage
विधि निर्धारित करती है कि मान को कॉन्फ़िगरेशन से संपत्ति में कैसे स्थानांतरित किया जाता है। यह आभासी है और इसे गैर-तुच्छ गुणों के लिए फिर से परिभाषित किया जा सकता है।
IConfigurationService
सेवा के आंतरिक संचालन की एक छोटी विशेषता: आप IObjectConverterService
का उपयोग नोटिस कर सकते हैं। इसकी आवश्यकता है क्योंकि इस मामले में IConfigurationService.GetValue
को Object
एक सामान्य पैरामीटर के साथ कहा जाता है और इस मामले में यह लोड किए गए तारों को संख्याओं में परिवर्तित नहीं करेगा, उदाहरण के लिए, इसलिए आपको यह स्वयं करने की आवश्यकता है।
इसी प्रकार बचत मापदंडों के साथ:
public void SaveToStorage(IConfigurationService configurationService) { foreach (var property in _configurationProperties) { try { SavePropertyToStorage(configurationService, property.ConfigurationKey, property.PropertyData); } catch (Exception ex) { Log.Error(ex, "Can't save to storage configuration property {Name}", property.PropertyData.Name); } } foreach (var property in _nestedConfigurationGroups) { var configurationGroup = GetValue(property) as ConfigurationGroupBase; if (configurationGroup == null) { Log.Error("Can't save to storage nested configuration group {Name}", property.Name); continue; } configurationGroup.SaveToStorage(configurationService); } } protected virtual void SavePropertyToStorage(IConfigurationService configurationService, String configurationKey, PropertyData propertyData) { Object value = GetValue(propertyData); configurationService.SetRoamingValue(configurationKey, value); }
यह ध्यान दिया जाना चाहिए कि कॉन्फ़िगरेशन मॉडल के अंदर, आपको समान पैरामीटर स्ट्रिंग कुंजी प्राप्त करने के लिए सरल नामकरण परंपराओं का पालन करने की आवश्यकता है:
- सेटिंग्स समूह (रूट को छोड़कर) के प्रकार "मूल" समूह के उपवर्ग हैं और कॉन्फ़िगरेशन में उनके नाम समाप्त होते हैं।
- ऐसे प्रत्येक प्रकार के लिए इसके अनुरूप एक संपत्ति होती है। उदाहरण के लिए,
ApplicationSettings
समूह और Application
गुण। संपत्ति का नाम कुछ भी प्रभावित नहीं करता है, लेकिन यह सबसे तार्किक और अपेक्षित विकल्प है।
व्यक्तिगत गुणों को सेट करना
ऑटो-कैटेल.फोडी और IConfigurationService
( IConfigurationService
में मूल्य की प्रत्यक्ष बचत और [DefaultValue]
विशेषता) का IConfigurationService
केवल सरल प्रकार और निरंतर डिफ़ॉल्ट मानों के लिए काम करेगा। जटिल गुणों के लिए, आपको कुछ अधिक प्रामाणिक पेंट करना होगा:
public partial class ConfigurationModel { public class ApplicationConfiguration : ConfigurationGroupBase { public CultureInfo PreferredCulture { get; set; } [DefaultValue("User")] public String Username { get; set; } protected override void LoadPropertyFromStorage(IConfigurationService configurationService, String configurationKey, PropertyData propertyData) { switch (propertyData.Name) { case nameof(PreferredCulture): String preferredCultureDefaultValue = CultureInfo.CurrentUICulture.ToString(); if (preferredCultureDefaultValue != "en-US" || preferredCultureDefaultValue != "ru-RU") preferredCultureDefaultValue = "en-US"; String value = configurationService.GetRoamingValue(configurationKey, preferredCultureDefaultValue); SetValue(propertyData, new CultureInfo(value)); break; default: base.LoadPropertyFromStorage(configurationService, configurationKey, propertyData); break; } } protected override void SavePropertyToStorage(IConfigurationService configurationService, String configurationKey, PropertyData propertyData) { switch (propertyData.Name) { case nameof(PreferredCulture): Object value = GetValue(propertyData); configurationService.SetRoamingValue(configurationKey, value.ToString()); break; default: base.SavePropertyToStorage(configurationService, configurationKey, propertyData); break; } } } }
अब हम उदाहरण के लिए, सेटिंग्स विंडो में किसी भी मॉडल गुण के लिए बाध्य कर सकते हैं:
<TextBox Text="{Binding Configuration.Application.Username}" />
ViewModel सेटिंग्स विंडो को बंद करते समय संचालन को ओवरराइड करना याद रखना याद रहता है:
protected override Task<Boolean> SaveAsync() { _applicationConfigurationProviderService.SaveChanges(); return base.SaveAsync(); } protected override Task<Boolean> CancelAsync() { _applicationConfigurationProviderService.LoadSettingsFromStorage(); return base.CancelAsync(); }
मापदंडों की संख्या में वृद्धि के साथ और, तदनुसार, इंटरफ़ेस की जटिलता, आप आसानी से प्रत्येक सेटिंग्स अनुभाग के लिए अलग व्यू और व्यूमॉडल बना सकते हैं।