I am currently involved in writing an ASP.NET MVC 4 web version (using the Razor view engine) of an existing (Delphi) desktop based software product which at present allows customers (businesses) to completely customise all of the text in their instance of the application, both to localise it and to customise it to their specific environments.
For example the terms-
My tasks
Products
Workflows
Designs
Might all be changed to individual terms used within the business.
At present this customisation is simply done within the text strings which are stored within the application database, and compared and loaded on every form load in the Delphi database. I.e. every string on the form is compared with the database English strings and a replacement based on the selected locale is rendered on the form if available. I don't feel this is either scalable or especially performant.
I am also not personally comfortable with the idea of customisation happening within the localization method, that every string in the application can be changed by the end customer - it can lead to support issues in terms of consistency in text, and confusion where instructions are incorrectly changed or not kept up to date. There are lots of strings within an application that probably should not be changed beyond localizing them to the locale of the user - local language and/or formatting conventions.
I personally would rather stick with the ASP.NET APIs and conventions in localizing the web version of the application, using RESX resource files and resource keys rather than string matching. This is much more flexible than string matching where strings may have different contexts or cases and cannot simply be changed en-mass (there many English words which may have different meanings in different contexts, and may not map to the same set of meanings in other languages), crucially avoids round trips to the database to fetch the strings needed to fetch the page and also allows for ease of translation with a great set of tools around the standard RESX files. It also means no custom implementation is needed to maintain or document for future developers.
This does however give a problem of how we cope with these custom terms.
I'm currently thinking that we should have a separate RESX file for these terms, which lists defaults for the given locale. I'd then create a new database table which will be something like
CREATE TABLE [dbo].[WEB_CUSTOM_TERMS]
(
[TERM_ID] int identity primary key,
[COMPANY_ID] int NOT NULL, -- Present for legacy reasons
[LOCALE] varchar(8) NOT NULL,
[TERM_KEY] varchar(40) NOT NULL,
[TERM] nvarchar(50) -- Intentionally short, this is to be used for single words or short phrases
);
This can potentially read into a Dictionary<string, string> when needed and cached by IIS to provide lookup without the delay in connecting to the SQL server and conducting the query.
public static class DatabaseTerms
{
private static string DictionaryKey
{
get { return string.Format("CustomTermsDictionary-{0}", UserCulture); }
}
private static string UserCulture
{
get { return System.Threading.Thread.CurrentThread.CurrentCulture.Name; }
}
public static Dictionary<string, string> TermsDictionary
{
get
{
if (HttpContext.Current.Cache[DictionaryKey] != null)
{
var databaseTerms = HttpContext.Current.Cache[DictionaryKey] as Dictionary<string, string>;
if (databaseTerms != null)
{
return databaseTerms;
}
}
var membershipProvider = Membership.Provider as CustomMembershipProvider;
int? companyId = null;
if (membershipProvider != null)
{
companyId = CustomMembershipProvider.CompanyId;
}
using (var context = new VisionEntities())
{
var databaseTerms = (from term in context.CustomTerms
where (companyId == null || term.CompanyId == companyId) &&
(term.Locale == UserCulture)
orderby term.Key
select term).ToDictionary(t => t.Key, t => t.Text);
HttpContext.Current.Cache.Insert(DictionaryKey, databaseTerms, null, DateTime.MaxValue,
new TimeSpan(0, 30, 0), CacheItemPriority.BelowNormal, null);
return databaseTerms;
}
}
set
{
if (HttpContext.Current.Cache[DictionaryKey] != null)
{
HttpContext.Current.Cache.Remove(DictionaryKey);
}
HttpContext.Current.Cache.Insert(DictionaryKey, value, null, DateTime.Now.AddHours(8),
new TimeSpan(0, 30, 0), CacheItemPriority.BelowNormal, null);
}
}
}
I can then have a class which exposes public properties, returning a string based on either this dictionary value or the value in the RESX file - whichever is not null. Something like-
public static class CustomTerm
{
public static string Product
{
get
{
return (DatabaseTerms.TermsDictionary.ContainsKey("Product") ?
DatabaseTerms.TermsDictionary["Product"] : CustomTermsResources.Product);
}
}
}
These can then be added to larger localised strings using string formatting if required, or used by themselves as labels for menus etc.
The main disadvantage of this approach is the need to anticipate in advance which terms the end customers may wish to customise, but I do feel this might present the best of both worlds.
Does this seem like a workable approach and how have other devs approached this problem?
Thanks in advance.
I once designed an MVC application, whereby any string could be changed. In my case it was to handle other languages, but conceivably you could change anything just for aesthetic purposes. That and there is potential for the system to be marketed to other shops, and they may well call the same things different name (You say "Deferred Payment", I say "Lease Payment", etc.)
Warning: This solution is not about globalization and localization (e.g. left-to-right, word/verb ordering - it only needed to do what it did!)
It also considered the possibility of American English (en-US) vs British English (en-GB) vs Australian English (en-AU).
In the end, A Locale table was created in the database:
_id _localeName _idRoot
---------------------------
1 en-GB null
2 en-US 1
3 en-AU 2
Note how US and AU effectively have en-GB as their parent. en-GB therefore had every conceivably string that can be used in the application, in our translation table:
_id _idCulture _from _to
--------------------------------------
1 1 msgyes Yes
2 1 msgno No
3 1 msgcolour Colour
4 2 msgcolour Color
Now, during application initalisation, there was a config flag that specified the culture, which in my case happened to be en-AU. The system looks up the culture tree (en-AU derives from en-GB), and loads all the translations bottom up in to a dictionary cache. Therefore any en-AU specific translations overwrote the GB ones.
So, to describe it in your case - you'd have ALL translations in your database anyway, and that's your default setup. When the customer wishes to customise the text, they basically get a new node (or a derived culture in my example), and you build your cache again. Any terms they customised override the defaults. You no longer have to worry about what terms were done, it just works.
We have a similar setup in our application, we allow certain modules to have a custom names to fit the customers brand.
the first step to this solution is we know our client context at runtime and we stuff it into the HttpContext.Items.
For those items that can be customized, we introduced resource file containing the base keys. If the enterprise wants it customized we add a prefix in front of the key name (ie Client_key)
At once all this is in place its a simple coalesce to fetch the customized or default value.
Resx file snippet
<data name="TotalLeads" xml:space="preserve">
<value>Total Leads</value>
</data>
<data name="Client_TotalLeads" xml:space="preserve">
<value>Total Prospects</value>
</data>
Class to handle switch between custom and base resources
public static class CustomEnterpriseResource
{
public static string GetString(string key)
{
return GetString(key, Thread.CurrentThread.CurrentUICulture);
}
public static string GetString(string key, string languageCode)
{
return GetString(key, new CultureInfo(languageCode));
}
public static string GetString(string key, CultureInfo cultureInfo)
{
var customKey = ((EnterpriseContext)HttpContext.Current.Items[EnterpriseContext.EnterpriseContextKey]).ResourcePrefix + key;
return Resources.Enterprise.ResourceManager.GetString(customKey, cultureInfo)
?? Resources.Enterprise.ResourceManager.GetString(key, cultureInfo);
}
}
Also to assist in the views we create a html helper for this.
public static class EnterpriseResourceHelper
{
/// <summary>
/// Gets a customizable resource
/// </summary>
/// <param name="helper">htmlHelper</param>
/// <param name="key">Key of the resource</param>
/// <returns>Either enterprise customized resource or base resource for current culture.</returns>
public static string EnterpriseResource(this HtmlHelper helper, string key)
{
return CustomEnterpriseResource.GetString(key);
}
}
The requirement you have is not very common. I have worked in projects where localization is done purely using satellite assemblies and in projects where localization is done purely using database tables. In .NET, the recommended approach is RESX files compiled into satellite assemblies. It is a real good approach if you adopt it fully.
Your requirements are some where in between. Though the approach you plan to take, at this point sounds good on paper, I have a feeling over the course of time, maintenance will be difficult, since some of the strings will be in RESX and some will be in database. Even if the distribution is 90% - 10%, people will have difficulty figuring out where it takes the strings when you have all the strings loaded up in production. You will get queries from your users why a particular string is not showing up correctly and at that time it can get difficult for a developer (other than you) to figure out. You will be the best judge for your needs but I believe either you embrace RESX approach fully (which is not possible in your case) or go the full database route. If I have every thing in tables, all I need to do is to run a query for a given profile and I will see all the strings. This will be easier to support.
Even with database, you can follow a RESX-style approach of storing the full string against a key for a culture. The older approach of storing word by word is definitely not a good solution and will not work for different languages, since only sentences can be translated and not individual words. Your idea of caching is definitely needed for performance. So, basically having every thing in a bunch of tables, caching the same in memory and pulling the strings from cache based on the current culture is something I will go for. Of course, my opinion is based on what I could understand by reading your question :).
Also, check this out.
Very interesting question, thanks for bringing it up.
I have localized applications in very different ways, and your case is very specific. Let's start from the fact that everything comes down to localizing the labels/titles of the UI. Therefore, these elements must become localizable. On many platforms (such as WinForms, ASP.NET) they are localizable by design, and all it takes is extending the resource management model. I would say, this is the most natural way of localization if you are writing for such a platform.
In case of ASP.NET MVC, even though it's built on top of ASP.NET engine, we are not recommended to use the ASP.NET's server side tags and therefore the solution does not work. Why I provided it above is to give the clarity to my solution which I'm describing below.
Step 1 - Modularity
All labels and titles are part of some particular screen of the application. Since the screen is what groups them, I often use it for this exact purpose when describing localization resources. BTW, this is why we have one resx file per screen for the applications. So, we are following the consistent standard here.
To express modularity, define classes that correspond to each screen, and have properties defined on it that correspond to each localizable label or title on the screen.
Pseudo example:
class ProductPageResources
{
public string PageTitle { get; set; }
public string ProductNameLabel { get; set; }
}
Step 2 - Localization
When designing your application screens, stick to the modular resource classes defined above. Use localized strings from the modular resource class to display the labels and titles. If there's a need to add a label to the screen, don't forget to add a new property to the modular resource class too. Since it's ASP.NET MVC, we need to pass the resources alongside with the model. Conceptually it would not be necessary, but doing so gives us the flexibility to replace the resource implementation in the future (e.g. from MS SQL to some other source).
Usage example:
#{
ViewBag.Title = string.format(Model.Resources.PageTitle, Model.Product.Name);
}
...
<label>#Model.Resources.ProductNameLabel</label>
Note that the resource class property returns the localized string for the current culture, or the fallback value (described below) if not found. For the default value to appear as a value, I prepare the resource object by iterating the properties and assigning default values to them if they are empty (because the override was not found).
Step 3 - Customization
[Very nice and descriptive term you've got here, so I will use it.]
I personally don't think that the resource management should be data-driven. Main reason is that it's not dynamic enough. Just recall, that we have modular classes, and we start adding properties to it when we need to display something new on the screen. On the other hand, if you add something to the database, it's not appearing on the screen just so.
Therefore, we have a strongly-typed localization here, and it's very natural way of localizing things. The rest comes from this conclusion.
On your customization/resource administration screen you can use reflection to detect all the modular resource classes and display their properties on the screen for customization. To find the resource classes, you can put them under the same namespace, or you could mark them with some attributes to easier find them in the assembly. Either way works.
To make the modular resource class more display-friendly, you can use attributes to assign descriptions that should display instead of their Pascal-Case names on the screen.
So, our modular resource class becomes something like this:
[Description("Product Page")]
class ProductPageResources
{
[Description("Page Title")]
[DefaultValue("Product Details: {0}")
public string PageTitle { get; set; }
[Description("Product Name (label)")]
[DefaultValue("Name:")]
public string ProductNameLabel { get; set; }
}
Basically, on the customization screen we will see default values for Product Page, and each available localized value. For the last part, you can enumerate all the active cultures of the application and extract the value from the property again. Alternatively, you can use some other way depending on the implementation.
This seems to be an extensive reflection, but after all, Visual Studio does something very similar by allowing us to edit the resource files in the special editor. Bottom line is that you have a precisely working framework.
Related
In the application I'm developing, the same software package serves many industries, and those industries have different vocabulary for what is essentially the same thing. The application is a C# server, to which a WPF desktop app makes socket-based XML requests.
For example, some customers may call something an "Item", some call it a "Part", or some call it a "SKU".
The goal is for the application to be able to relabel itself based on a "Vocabulary" setting that we create for the user. Typically, a given customer's vocabulary will only differ by perhaps 5-25 words/phrases out of the entire application. These custom vocabularies are specific to/created by the customer, and wouldn't be kept with the main application distribution.
My initial thought was to do this with custom CultureInfo, (e.g. "en-AC" for "Acme" company), supply just values that differ from the base en-US in that resource file.
The en-AC.resx resource could be kept on the server, loaded by the server, and also transmitted for loading into the WPF client app.
Problem with that thus far seems to be that the ResourceManager does not correctly pick strings for custom cultures, a'la this thread, and I've not been able to solve that yet. As well, as the app is ClickOnce deployed, we may not have permission to register a new culture.
My next thought, since the number of phrases to modify is so small, was to replace the resource value at runtime, but that seems to be a bit of a no-no as well, searching around.
So, thought I would ask the community for their suggestions on how to handle this.
Open to suggestions and ideas...
Because it's only about a few words I think I'd do it via a naming convention. Suppose you defined the string key "MyCompany". You usually access this way:
string myString1 = Properties.Resource.MyCompany;
But it is also ok to Access it that way:
string myString2 = Properties.Resource.ResourceManager.GetString ("MyCompany")
It's exactly the same (but dealing with strings as identifiers - which is somewhat error prone). What you now can do is to check for a special name first that you syntesize like "MyCompany_AC". The drawback is you need your own wrapper for each string:
string MyCompany
{
get
{
string myString = Properties.Resource.ResourceManager.GetString ("MyCompany_" + theCompanyPostfix);
if (myString == null)
{
myString = Properties.Resource.ResourceManager.GetString ("MyCompany");
}
return myString;
}
}
I am trying to create something to hold global site-wide settings in our ASP.NET website - things such as site name, google analytics account number, facebook url etc... The site can have multiple ‘brands’ or sub-sites associated with it, hence the sitguid column, we would also like to have the option to put them into groups, hence the group column – e.g. TEST and PRODUCTION (set via web.config appsetting).
I do not want any of the KEY’s to be hardcoded anywhere, but I would like to be able to reference them as simply as possible in code, e.g. SiteSetting.getSetting(“SiteName”) (these may be used in templates (masterpages and such) that more junior devs will create)
I would also like to be able to administer the existing settings in our admin console and to be able to create new settings.
The datatype column is for the edit form so that the correct input element can be used, e.g. checkbox for bit types, text box for varchar etc...
SiteSettings database table currently:
[sts_sitGuid] [uniqueidentifier] NOT NULL, -- tells us which site the setting is for
[sts_group] [nvarchar](50) NOT NULL, -- used to group settings e.g. test/live
[sts_name] [nvarchar](max) NULL, -- the display name of the setting, for edit forms
[sts_alias] [nvarchar](50) NOT NULL, -- the name for the setting
[sts_value] [nvarchar](max) NOT NULL, -- the settings value
[sts_dataType] [nvarchar](50) NULL, -- indicates the control to render on edit form
[sts_ord] [tinyint] NULL, -- The order they will appear in on the admin form
I am part way through having this working at the moment, but I am not happy with the way I have done it and would like any advice people here have that might help find the ‘right’ solution! I'm sure people have done this before me. (I would share what I have so far, but do not want to skew the answers in any particular way) All i'm looking for is an overview of how this might be best done, not looking for anyone to write it for me ;)
I’ve done quite a bit of searching both here and Google and have not really found what I’m looking for, especially the ability to add new setting ‘definitions’ as well as editing the settings that exist.
The system runs on ASP.NET using webforms, it's all written in c# and uses MSSQL 2008.
As always, any help is very much appreciated!
EDIT: To clarify I am going to explain what I have built so far. I am dead set on storing all of this in SQL as we don't want web.config or other xml files or another database floating around since it'll give us more to do when we rollout the app to other customers.
So far I have a SiteSettings class, this has a method GetSetting which i can call with GetSetting("SettingAlias") to get the value for "SettingAlias" in the DB. This class's constructor fetches all the settings for the current site from the database and stores those in a dictionary, GetSetting reads from that dictionary. All of that part I am happy with so far.
The part I am struggling with is generating the edit form. The previous version of this used a webservice to get/set the settings and I am trying to continue using something similar to save work, but they were all defined in the code, such as GoogleAnalyticsID, Sitename etc... and each had a column in the database, the change I am making is to store these settings as ROWS instead (since then it's easier to add more, no need to change the schema & all of the sitesettings class) Currently my SiteSettings class has a SiteSettingsEditForm method which grabs all the info from the db, creates a bunch of controls for the form elements, puts that in a temporary page and executes that, then passes the HTML generated to our management system via ajax. This feels wrong and is a bit clunky, and is the reason for posting it here, I am having trouble figuring out how to save this stuff back via the webservice, but more importantly generating a bunch of HTML by executing a page containing a load of form controls just feels like the wrong way to do it.
So in summary I (think i) want to write a class to be able to cache & read a handful of rows from a database table, and also give me an edit form (or give data to something else to generate the form) that is dynamic based on the contents of the same database table (e.g. where my type column is 'bit' I want a checkbox, where it is 'text' I want a text input)
Sometimes this kind of problem is easier to visualize if you start off with the data model. If you want a setting per row, then two tables would probably be the best way to store this:
Site:
SiteId SiteKey SiteName
1 XYGZ4345 Client Site 1
2 AZT43752 Client Site 2
This would define the list of sites you have config for. I'd use a SiteKey as you'd put this in your web.config and it's better to abstract this away into a random string or GUID (to make it harder to accidentally load someone else's config), the client can change their name and you don't get confused in the future as you didn't use their old name as a key etc etc.
The config table itself is also simple, if we treat every setting as a string:
SiteSetting:
SettingId SiteId SettingName SettingValue
1 1 Brand KrustyBrand
2 1 GoogleId MSFTSUX0R
3 2 Brand ConfigMasters(TM)
You can then load all the config quite simply:
SELECT * FROM SiteSetting INNER JOIN Site ON (SiteSetting.SiteId = Site.SiteId) WHERE Site.SiteKey = 'XYGZ4345'
Now we have a list of key value pairs you could then store in a class like:
public class SiteSetting
{
public Site Site {
get; set; //Site would just be a simple class consisiting of Id, Key and Name to match the database table
}
protected Dictionary<String, String> Settings { get; set; } //Simple key value pairs
}
So this is a very simple solution. However, we can take it further - things to consider:
1) Can we add an environment to this somewhere?
We could either add a site per environment
OR
Add an environment to the SiteSetting table. The advantage of this is that you could define enironment = 'ALL' to avoid duplication.
OR
The database the configuration is loaded from defines the environment; so you change the config connection string in the app config. Of course, to connect to a different environment you have to change app.config, but you would potentially have to do that anyway to change the client key and/or environment.
2) Add the concept of user defineable settings - some settings you are going to want to change, some you are going to want to lock. A bit column containing "UserDefinable" would allow you to sort this out
3) Typing of settings.
This is slightly more difficult. You might have something like:
PropertyId PropertyName PropertyType Format UserDefined
1 Brand String NULL 1
2 DatePresentation DateTime "yyyy-MM-dd" 1
The Settings table then only defines a value, and a PropertyId. The advantage of this is that you can then start to increase the information about each setting you are storing, and reuse this information as the design is more normalized. The Settings class then changes like so:
public List<PropertyValue> { get; set; } //replacing the dictionary
PropertyValue then looks something like:
public class PropertyValue
{
int Id { get; set; }
public string Name { get; set; }
public string Value { get; set; }
public string PVType { get; set; } //Could be an enum
public string DisplayFormat { get; set;
private string _RawValue;
public string Value{
get{
switch(PVType){
case "DateTime":
return Convert.ToDateTime(_RawValue).ToString(DisplayFormat);
break;
case "Double":
return Convert.ToDouble(_RawValue).ToString(DisplayFormat);
break;
default:
return _RawValue;
}
}
set{
_RawValue = value;
}
}
}
Things like the Value method need to be improved to support robust error handling (you could also investigate using Convert.ChangeType to simplify the switch block)
This topic is as simple or as complicated as you choose to make it ;-)
Editing
As regards maintaining them; a very simple GUI would allow the user to see all of their properties in a tabular format. You might consider having rows where UserDefinable = 0 as readonly, otherwise the user can edit and add rows. You'd need to validate, especially for duplicate Setting Names for example.
The easiest way to do this is to use the DataGrid; a simple mockup might look something like this:
And a more sophisticated approach might look something like this
Generating the form is therefore as simple as databinding a collection of PropertyValue objects to your chosen grid solution.
As you probably found, there are a variety of ways that you can do this, ranging from Microsoft-recommended to 100% roll-your-own. You're currently on the roll-your-own end of the spectrum, which I think is usually better anyway.
Anyway, basically what you're looking for is a StateBag. Something loosely typed and highly configurable, which doesn't really lend itself very well to an RDBMS, but if you already have the SQL Server in place, it's probably going to be easiest to just use that.
Option 1: Redis
However, another option is to run something like Redis for site configurations. You can store it as a collection of key/value pairs in a Redis object, pull it in a JSON object, and then either convert that to a Hashtable, where lookups will be very fast, or some other hashable collection. A simple wrapper class around the collection would suffice.
Redis is very light and in your case wouldn't require a lot of configuration or maintenance.
Option 2: SQL Server
The way you've mentioned is actually a pretty good way of doing it, but rather than query the database every time, put a strongly typed collection or Hashtable in the cache and let it expire every few hours.
If you go with essentially what you have now, you could store it like this:
Setting
SettingId Int PK,
Key NVarchar(50),
Name NVarchar(50),
Description NVarchar(1000),
Type NVarchar(50),
Priority Int
Configuration
SiteId Int,
SettingId Int FK(Setting),
SettingValue NVarchar(1000)
Don't store things like live/test/dev in the database. You need to have completely separate databases and servers to distinguish between live/test/dev/stage/QA etc.
im currenctly doing this by creating a class which have all the settings as propertiese like
class Setting
{
GUID siteGuid{get; set;}
//other settings
}
then i created a static class SettingManager like this
public static class SettingManager
{
private ConcurrentDictionary<GUID,Setting> settings= new ConcurrentDictionary<GUID,Setting>;
GetSetting(Guid siteGUID)
{
settings.TryGet(siteGuid);
Lastrefreshed = DateTime.Now;
//other code
}
Private DateTime LastRefreshedOn = DateTime.Now;
public void PopulateSetingsDic()
{
//populate the settings dictionary by getting the values from the database
}
}
now anywhere in your code just include the namespace and use the settings.
u can populate the settings once or on every interval in application_start using lastRefreshedOn variable
it will be fast because u have all the settings inside the memory.
also if u want that the u should be able to add the settings dynamically then u can use an ExpandoObject and add settings dynamically using the column names from the database or just a mapping
then ull be able to use the settings by casting the settings ExpandoObject to IDictionary<string,object> here string can be the string converted GUID
Edit:- http://zippedtech.blogspot.in/2012/04/dynaminism-in-net-40.html check the link.. i have added a new post for solution to problems like this.
I would use xml, make a class that can take xpath as your "key"
Ex.
MySett.get("//level1/mysetting")
or even
MySett.get("//mysetting")
where each one can return a collection, just the first one, or what ever you want.
You could even overload.
I like xml because of it's great flexability, and to reduce code elsewher, just write a class.
Downside, You need to load your document at application startup and save at shutdown.
Here is an example class in vb code. (c code would still be very similar, I just used vb because it was up at thetime
Imports System.Xml
Public Class XSett
Public xml As XmlDocument
Public Overloads Function gett(ByVal xp As String)
Return CType(xml.SelectSingleNode(xp), XmlElement).InnerXml
'by using inner xml, you can have either text setting
'or more markup that you might need for another function
'your choice. you could even cast it into another instance
'of this class
End Function
Public Overloads Function gett(ByVal xp As String, ByVal sel As Integer)
Return CType(xml.SelectNodes(xp)(sel), XmlElement).InnerXml
'here, you can have multiple and choose the one you want
End Function
Public Overloads Sub gett(ByVal xp As String, ByRef col As Collection)
Dim i As Integer
Dim nds = xml.SelectNodes(xp)
For i = 0 To nds.Count - 1
col.Add(CType(nds(i), XmlElement).InnerXml)
Next
'Creted an entire collection of elemens.
'i used vb's "collection" object, but any collection would do
End Sub
Public Overloads Sub sett(ByVal ap As String, ByVal name As String, ByVal data As String)
'assume add here.
'ap asks for existing parent element. eg: //guids
'name ask for name of setting element
Dim ts = xml.CreateElement(name)
ts.InnerXml = data
If ap = "" Then 'we assume document element
xml.DocumentElement.AppendChild(ts)
Else
Dim p = CType(xml.SelectSingleNode(ap), XmlElement)
p.AppendChild(ts)
End If
End Sub
Public Overloads Sub sett(ByVal xp As String, ByVal sel As Integer, ByVal data As String)
'just change existing setting
CType(xml.SelectNodes(xp)(sel), XmlElement).InnerXml = data
End Sub
'naturally you can expand infinitely if needed
End Class
If I understand your question correctly you are looking for a centralized configuration framework. For configuration & server management I would normally recommend Chef or Puppet however for ASP.NET I did some quick googling and it seems like the WCF based Configuration Service might do the trick for you. The document I linked to is a step by step tutorial for the configuration service used in the .NET StockTrader 5 Sample Application.
This question arose when I was trying to figure out a larger problem that, for simplicity sake, I'm omitting.
I have to represent a certain data structure in C#. Its a protocol that will be used to communicate with an external system. As such, it has a sequence of strings with predefined lengths and integer (or other, more complicated data). Let's assume:
SYSTEM : four chars
APPLICATION : eight chars
ID : four-byte integer
Now, my preferred way to represent this would be using strings, so
class Message
{
string System {get; set; }; // four characters only!
string Application {get; set; }; // eight chars
int Id {get; set; };
}
Problem is: I have to ensure that string doesn't have more than the predefined length. Furthermore, this header will actually have tenths of fields, are those will change every now and then (we are still deciding the message layout).
How is the best way to describe such structure? I thought, for example, to use a XML with the data description and use reflection in order to create a class that adheres to the implementation (since I need to access it programatically).
And, like I said, there is more trouble. I have other types of data types that limits the number of characters/digits...
For starters: the whole length issue. That's easily solved by not using auto-properties, but instead declaring your own field and writing the property the "old-fashioned" way. You can then validate your requirement in the setter, and throw an exception or discard the new value if it's invalid.
For the changing structure: If it's not possible to just go in and alter the class, you could write a solution which uses a Dictionary (well, perhaps one per data type you want to store) to associate a name with a value. Add a file of some sort (perhaps XML) which describes the fields allowed, their type, and validation requirements.
However, if it's just changing because you haven't decided on a final structure yet, I would probably prefer just changing the class - if you don't need that sort of dynamic structure when you deploy your application, it seems like a waste of time, since you'll probably end up spending more time writing the dynamic stuff than you would altering the class.
I have the below enum that I use for one of my filters and suit well to my object model
public enum ColorGroups
{
White = 1,
Brown = 2,
Red = 3,
Black = 4
}
My concern is in the future when a client want to another color to the collection how do I extend the collection. I want the system to be fully dynamic and does not require a technical person to alter the code for such things..
If you want the data to be user-editable, it may not be suitable to use an enum. Enums are compile-time units, so will require a developer (or some hacky code generation).
Instead, consider using a database table for this data, pre-populated with your items (and perhaps with a "System" column to control which ones are user-defined vs required by the system). Then changes are just inserts (etc) to the table.
You can, of course, use any other storage mechanism - for example, a delimited string in a config file - but I'm guessing you'll want a database somewhere in the system?
An enum may not be the right tool for the job in that case. You would be better off using a set of configuration options. These could be in a config file, in the registry or in a database, depending upon what is available to you and whether you want the configuration to be undertaken by a developer or consultant, or by the users of the system.
I want the system to be fully dynamic and does not require a technical person to alter the code for such things..
"Fully dynamic" and "use an enum" are mutually exclusive if you don't want a technical person to have to get involved to make changes. A database or a configuration file is a better choice here.
I have a console application that I am rebuilding from C to C#. This application has to be able to support the legacy method of storing information like parameters from a command-line and parameters from a file (called the system parameters) that customize each run. The system parameters file is in plain-text with a simple key-value structure.
My questions are:
Should I combine these different parameters into a single Configuration object?
How would I call this configuration object from the code to store parameters?
How would I call this configuration object from the code to retrieve parameters?
Should this object be strongly-typed?
I will need access to this structure from a lot of different places in the code. What is the most elegant way to retrieve the values in the object without passing the object itself around everywhere?
I have a feeling that it should be a single, strongly-typed object and that it should be an instantiated object that is retrieved from a repository with a static retrieval method however I really want validation of this method.
I would use a single configuration object like the following:
using System;
using System.IO;
using System.Reflection;
public sealed class Setting {
public static int FrameMax { get; set; }
public static string VideoDir { get; set; }
static readonly string SETTINGS = "Settings.ini";
static readonly Setting instance = new Setting();
Setting() {}
static Setting() {
string property = "";
string[] settings = File.ReadAllLines(SETTINGS);
foreach (string s in settings)
try {
string[] split = s.Split(new char[] { ':' }, 2);
if (split.Length != 2)
continue;
property = split[0].Trim();
string value = split[1].Trim();
PropertyInfo propInfo = instance.GetType().GetProperty(property);
switch (propInfo.PropertyType.Name) {
case "Int32":
propInfo.SetValue(null, Convert.ToInt32(value), null);
break;
case "String":
propInfo.SetValue(null, value, null);
break;
}
} catch {
throw new Exception("Invalid setting '" + property + "'");
}
}
}
Since this is a singleton, it will create one and only one instance of itself the first time a public static property is referenced from the Setting object.
When the object is created, it reads from the Settings.ini file. The settings file is a plain-text file with a simple key : value structure that might look like this:
FrameMax : 12
VideoDir : C:\Videos\Best
The object uses reflection to discover each property and to store its initial value. In this example, two properties have been defined:
public static int FrameMax { get; set; }
public static string VideoDir { get; set; }
The code as written handles Int32 and String types. By adding additional case statements to the switch statement, you could easily add support for types like Float and Decimal.
To change a setting, you would use something like:
Setting.FrameMax = 5;
To retrieve a setting, you would use something like:
if (Setting.FrameMax > 10) ...
You'll notice that all the properties are strongly-typed. Also, you don't have to pass the Setting object around, as all the Setting properties are static and always available everywhere.
I hope this idea is helpful.
I like using Settings. These can be generated automatically either by creating a settings file using the Add New File dialog box, or by adding a default settings file from project properties.
Each setting may be in user or application scope, which controls whether or not the user can change them or they are restricted to their default values. They are easily saved with the Save() method and loaded automatically into the static Default property.
This class seems to be for application or user-based settings. I'm looking for per-run settings. Would you still recommend using this class in that case? – x97mdr
Yes. If you have both user/application based settings and per-run settings you should use two different classes - the normal (saved) settings and the per-run settings.
As long as you don't save the per-run settings, you should be safe and settings are still quite easy to use. These are static settings though. If the same application run needs several instances - this is the wrong approach.
I find that whenever I have to deal with a legacy system, sticking with the old format almost always works best. Often times there are other people using the legacy formats for other tasks (like automation of the app, for example), so if you recode the way the application handles inputs, you might break other systems.
On the other hand, if you are pretty confident that you know all the people using the system, and they tell you that they don't care if you change these types of things, I would probably move everything to XML. Besides all the nice features of XML from an application point of view (like being in ASCII so it's easily modified by humans, being self-documenting, etc ...), XML is also time-saving, in that you don't have to write your own I/O or parser. There's already a wide variety of libraries out there, particularly in .NET 3.0/3.5, that do very well. (As you're moving to C#, I'm guessing you're already thinking along these lines :)
So ultimately, you'd have to base your decision on cost-to-implement: if you lower your cost of implementation by moving to XML or similar, make sure that you don't raise other people's cost of implementation to move to your new application framework.
Good luck!
XmlDocument - you can generate a class definition using XSD.exe