In my ASP MVC Application I am populating the content present in a view based on data from XML files.
What my question is, am I doing this the best way? Surely there has got to be a more efficient and easier way to do what I'm doing here.
Here is an example of the markup in my XML file:
<?xml version="1.0" encoding="utf-8" ?>
<Content>
<!-- P1: Customer details -->
<ContentItem>
<key>Header_CancelImg</key>
<value>~/Content/mainpage/images/close.gif</value>
</ContentItem>
<ContentItem>
<key>Header_CancelText</key>
<value>Cancel this application</value>
</ContentItem>
So, I am deserialzing the contents of this XML file like so:
using (MemoryStream ms = new MemoryStream(System.IO.File.ReadAllBytes(path))) {
XmlSerializer serializer = new XmlSerializer(typeof(Content));
pageContent = serializer.Deserialize(ms) as Content;
}
All good. Now, based on this, what is the best way I can populate my view based on this content? Let me show you what I mean, and how I'm doing it now (very horribly):
#model <project.Models.Content> // This content object contains Content object where the deserializer is present as shown above
#foreach (var contentItem in Model.Item2.contentItemList)
{
#if(contentItem.key == "Header_CancelImg")
{
<img src="#Url.Content(contentItem.value)">
continue;
}
#if(contentItem.key == "Header_CancelText")
{
<p>#contentItem.value</p>
continue;
}
} // and so on
Is there a more easier way I can do this?
Thanks
If you gave your Content class a default property, you could access the data from your model by referencing the key names without having to iterate over all the different possible keys. I have shown a private dictionary property which is lazily initialized on the first hit.
public class Content
{
private Dictionary<string, string> contentItems;
public string this[string key]
{
if (contentItems == null)
{
contentItems = contentItemList.ToDictionary(i => i.Key, i => i.Value);
}
if (contentItems.ContainsKey(key))
{
return contentItems[key];
}
return string.Empty;
}
//other properties
}
Then your razor code could be like this:
#model project.Models.Content
<img src="#Url.Content(Model["Header_CancelImg"])">
<p>#Model["Header_CancelText"]</p>
Related
Sitecore comes with several standard custom value tokens when creating branch templates (i.e. $name for the name of the new item, $parentid, for the id of the parent).
Is there anyway to add new variables?
Specifically I want a variable that will allow me to access the items path when added?
There is a sitecore blog post for this ADD CUSTOM STANDARD VALUES TOKENS IN THE SITECORE ASP.NET CMS but TBH, it's wrong . I'm not sure why sitecore insist on producing "untested prototype(s)" all the time in these posts. The guy in that blog literally says You can implement a solution based on the following untested prototype o_O
For some reason sitecore are jumping though various hoops to decompile the source and then recreate it (give a man a hammer and everything looks like a nail maybe?). This makes your code very fragile should the default behaviour change and is just totally unnecessary.
You can add a new variable in a few lines of code:
public class NewVariablesReplacer : MasterVariablesReplacer
{
public override string Replace(string text, Item targetItem)
{
//still need to assert these here
Sitecore.Diagnostics.Assert.ArgumentNotNull(text, "text");
Sitecore.Diagnostics.Assert.ArgumentNotNull(targetItem, "targetItem");
string tempTxt = text;
if (text.Contains("$path"))
{
Sitecore.Diagnostics.Assert.ArgumentNotNull(targetItem.Paths, "targetItem.Paths");
Sitecore.Diagnostics.Assert.ArgumentNotNull(targetItem.Paths.FullPath, "targetItem.Paths.FullPath");
tempTxt = text.Replace("$path", targetItem.Paths.FullPath);
}
//Do what you would normally do.
return base.Replace(tempTxt, targetItem);
}
}
This works without decompiling because it retains the base functionality by calling base.Replace(text, targetItem);.
You then need to alter the default behaviour in the xml as in the blog post:
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
<sitecore>
<settings>
<setting name="MasterVariablesReplacer">
<patch:attribute name="value">Sitecore.Sharedsource.Data.NewVariablesReplacer ,Sitecore.Sharedsource</patch:attribute>
</setting>
</settings>
</sitecore>
</configuration>
Out of the box, we have these variables at our disposal:
$name: The item name
$id: The item ID
$parentid: The item ID of the parent item
$parentname: The item name of the parent item
$date: The system date
$time: The system time
$now: The combination of system date and time
Tokens are variables that start with the “$” symbol. When a content item is created in the content tree, the pipeline that gets invoked is:
<expandInitialFieldValue help="Processors should derive from Sitecore.Pipelines.ExpandInitialFieldValue.ExpandInitialFieldValueProcessor">
<processor type="Sitecore.Pipelines.ExpandInitialFieldValue.SkipStandardValueItems, Sitecore.Kernel" />
<processor type="Sitecore.Pipelines.ExpandInitialFieldValue.CheckSharedField, Sitecore.Kernel" />
<processor type="Sitecore.Pipelines.ExpandInitialFieldValue.ReplaceVariables, Sitecore.Kernel" /></expandInitialFieldValue>
The pipeline that does all the work is:
public override void Process(ExpandInitialFieldValueArgs args)
{
Assert.ArgumentNotNull((object) args, "args");
MasterVariablesReplacer variablesReplacer = Factory.GetMasterVariablesReplacer();
string text = args.SourceField.Value;
if (variablesReplacer == null)
args.Result = text;
else
args.Result = variablesReplacer.Replace(text, args.TargetItem);
}
In the ReplaceVariables processor, you will see that there is a call to another class that does all the work. This class is defined in the section of web.config.
<setting name="MasterVariablesReplacer" value="Sitecore.Data.MasterVariablesReplacer,Sitecore.Kernel.dll" />
Decompile this class and you will see that the execution order is Replace > ReplaceValues > ReplaceWithDefault with Replace being a virtual method while the others are not. Fortunately for us, this means we can easily override the combined logic with a custom subclass of our own.
<!--<setting name="MasterVariablesReplacer" value="Sitecore.Data.MasterVariablesReplacer,Sitecore.Kernel.dll" />-->
<setting name="MasterVariablesReplacer" value="Client.SitecoreUtil.SettingsOverrides.MasterVariablesReplacer,Client.Sitecore" />
In our custom class, we have to override the Replace method with the same or similar code. Then we need two local private versions of ReplaceValues and ReplaceWithDefault. We can use the same or similar code for ReplaceWithDefault but the ReplaceValues method is where you would define your custom tokens and also tell Sitecore what to do with it. For example, let’s say you want to replace the custom “$test” token with the string “hello” this would be the resulting code.
private string ReplaceValues(string text, Func<string> defaultName, Func<string> defaultId, Func<string> defaultParentName, Func<string> defaultParentId)
{
if (text.Length == 0 || text.IndexOf('$') < 0)
return text;
ReplacerContext context = this.GetContext();
if (context != null)
{
foreach (KeyValuePair<string, string> keyValuePair in (SafeDictionary<string, string>)context.Values)
text = text.Replace(keyValuePair.Key, keyValuePair.Value);
}
text = this.ReplaceWithDefault(text, "$name", defaultName, context);
text = this.ReplaceWithDefault(text, "$id", defaultId, context);
text = this.ReplaceWithDefault(text, "$parentid", defaultParentId, context);
text = this.ReplaceWithDefault(text, "$parentname", defaultParentName, context);
text = this.ReplaceWithDefault(text, "$date", (Func<string>)(() => DateUtil.IsoNowDate), context);
text = this.ReplaceWithDefault(text, "$time", (Func<string>)(() => DateUtil.IsoNowTime), context);
text = this.ReplaceWithDefault(text, "$now", (Func<string>)(() => DateUtil.IsoNow), context);
text = this.ReplaceWithDefault(text, "$test", (Func<string>)(() => "hello"), context);
return text;
}
That is all there is to define custom token variables for Sitecore standard values. All the work is done in the ReplaceValues method.
I have made a script in C# that should load data from a XML file into a ListView.
This is the XML file I used to test :
<?xml version="1.0" encoding="utf-8"?>
<Items>
<wordExample languageOne="Пока" languageTwo="Doei" languageThree="Goodbye" />
<wordExample languageOne="1" languageTwo="2" languageThree="3" />
<wordExample languageOne="4" languageTwo="5" languageThree="6" />
<wordExample languageOne="7" languageTwo="8" languageThree="9" />
</Items>
Now I get an error when I try to load the XMl into the ListView and I really don't know what it could be, this is actually the first time I try to use XML in C#.
This is the code used to load the XML into the ListView :
public void ImportXMLToListView(ListView listview)
{
DialogResult dr = OPEN_FILE_DIA.ShowDialog();
if (dr == DialogResult.OK)
{
XDocument doc = XDocument.Load(OPEN_FILE_DIA.FileName);
int counter = 0;
foreach (var dm in doc.Descendants("Items"))
{
string tmpOne = dm.Attribute("languageOne").Value;
string tmpTwo = dm.Attribute("languageTwo").Value;
string tmpThree = dm.Attribute("languageThree").Value;
counter++;
ListViewItem lvi;
lvi = new ListViewItem(tmpOne);
lvi.SubItems.Add(tmpTwo);
lvi.SubItems.Add(tmpThree);
listview.Items.Add(lvi);
}
}
}
Am I doing something wrong??
This is the error I get : (Object reference not set to an instance of an object.)
Please tell me what I do wrong I really try to understand :S
language attributes belongs to your wordExample items. You need doc.Descendants("wordExample")
foreach (var dm in doc.Descendants("wordExample"))
{
string tmpOne = (string)dm.Attribute("languageOne");
string tmpTwo = (string)dm.Attribute("languageTwo");
string tmpThree = (string)dm.Attribute("languageThree");
...
}
And you can use explicit cast instead of directly accessing Value property to avoid NullReferenceException.
I have been trying for quite a while to find a solution to a problem which ought to be pretty straight forward.
What I want is variables that can be edited in the iPhone settings menu while the app is not running. Basically a config file wrapped in the iOS GUI.
This is supposed to be an built-in feature in iOS, and while I can find some methods related to it, I can't find an actual solution.
The closest I've been to getting what I want is where it works like any other variable: Empty on application start, and gets scratched again on application close. And still not visible in the iPhone Settings window.
This is the code I have:
private void LoadSettingsFromIOS()
{
// This is where it works like any other variable. Aka. gets scratched on app closing.
_thisUser.SetValueForKey(new NSString("Blargh"), new NSString("SaveCredentials"));
string stringForKey = _thisUser.StringForKey("SaveCredentials");
// This is where I'm supposed to be able to load the data from settings and set the checkbox's 'On' state to the value. Currently it always returns False.
bool saveCredentials = _thisUser.BoolForKey("SaveCredentials");
chckBoxRememberMe.On = saveCredentials;
}
And my Settings.Bundle Root.pList file:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>PreferenceSpecifiers</key>
<array>
<dict>
<key>Type</key>
<string>PSToggleSwitchSpecifier</string>
<key>Title</key>
<string>Credentials</string>
<key>Key</key>
<string>SaveCredentials</string>
<key>DefaultValue</key>
<true/>
</dict>
</array>
<key>StringsTable</key>
<string>Root</string>
</dict>
</plist>
Anyone out there who's been messing with Xamarin iOS and knows how this works?
EDIT: Here is a working project in Xamarin: https://github.com/xamarin/monotouch-samples/tree/master/AppPrefs
First, you need to make your view visible in the iPhone Settings window.
To do that, you need to create a folder named "Settings.bundle" in the top-level directory of your app’s bundle. Then, create new file named "Root.plist". The file has to be of the type Property List.
You do that by right-clicking Settings.bundle, then Add -> New File... -> iOS (on the left pane) -> Property List. If you add an empty file and then rename it as .plist, it won't show in iPhone's Settings.
The first element in your Root.plist has to be an Array, and it must contain Dictionaries.
You can find more info here on how to construct your Settings View:
https://developer.apple.com/library/ios/DOCUMENTATION/Cocoa/Conceptual/UserDefaults/Preferences/Preferences.html
Pay attention to Figure 4-2 (Strings Filename is not necessary). Also, edit the Root.plist file in the Xamarin editor, it is much easier.
To get values from the newly created Settings, you can use this class (Add as many properties as you want):
public class Settings
{
public static string ApiPath { get; private set; }
const string API_PATH_KEY = "serverAddress"; //this needs to be the Identifier of the field in the Root.plist
public static void SetUpByPreferences()
{
var testVal = NSUserDefaults.StandardUserDefaults.StringForKey(API_PATH_KEY);
if (testVal == null)
LoadDefaultValues();
else
LoadEditedValues();
SavePreferences();
}
static void LoadDefaultValues()
{
var settingsDict = new NSDictionary(NSBundle.MainBundle.PathForResource("Settings.bundle/Root.plist", null));
if (settingsDict != null)
{
var prefSpecifierArray = settingsDict[(NSString)"PreferenceSpecifiers"] as NSArray;
if (prefSpecifierArray != null)
{
foreach (var prefItem in NSArray.FromArray<NSDictionary>(prefSpecifierArray))
{
var key = prefItem[(NSString)"Key"] as NSString;
if (key == null)
continue;
var value = prefItem[(NSString)"DefaultValue"];
if (value == null)
continue;
switch (key.ToString())
{
case API_PATH_KEY:
ApiPath = value.ToString();
break;
default:
break;
}
}
}
}
}
static void LoadEditedValues()
{
ApiPath = NSUserDefaults.StandardUserDefaults.StringForKey(API_PATH_KEY);
}
//Save new preferences to Settings
static void SavePreferences()
{
var appDefaults = NSDictionary.FromObjectsAndKeys(new object[] {
new NSString(ApiPath)
}, new object[] {
API_PATH_KEY
});
NSUserDefaults.StandardUserDefaults.RegisterDefaults(appDefaults);
NSUserDefaults.StandardUserDefaults.Synchronize();
}
}
You just call SetUpByPreferences() (which is the only public method), and then get the values from the properties in the class.
Try this:
NSBundle.MainBundle.PathForResource ("Settings", #"bundle");
I am not actually using the Razor from ASP.NET MVC, I am using the standalone version as found here
I have created my own HtmlHelper as described here
I have determined via trial and error that razor <text> attributes produce a Func<Object, TemplateWriter> object when called in the context of a method.
The method signature of the helper looks like:
public String IncludeOnce(Func<Object, TemplateWriter> text) {
//here I need to be able to render the text Func to a string so
//I can do some checks, and return it if it hasnt yet been included
//or return an empty string if it has
}
I am invoking it in my template like:
#Html.IncludeOnce(
#<text>
<style type="text/css">
/* styles I only want on the page once, and not everytime the template
is rendered. Note: I need #Model to work here too*/
.something { top: #Model.Top }
</style>
</text>)
How can I get it as a String? Also, If I pass it to another template, eg:
public String IncludeOnce(Func<Object, TemplateWriter> text) {
return Razor.Parse("other.cshtml", new { Content = text(new Object()) })
}
where other.cshtml is:
#Model.Content
it works. What does Razor.Parse know that I dont?
Thanks!
I'm assuming the Object parameter is the Model. So, by looking at your code above, I'm pretty sure the following will work:
public String IncludeOnce(Func<Object, TemplateWriter> text) {
string output = text(Model).ToString();
//Do Stuff
return output
}
I've been storing collections of user settings in the Properties.Settings.Default object and using the Visual Studio settings designer (right-click on your project, click on Properties and then click on the Settings tab) to set the thing up. Recently, several users have complained that the data this particular setting tracks is missing, randomly.
To give an idea (not exactly how I do it, but somewhat close), the way it works is I have an object, like this:
class MyObject
{
public static string Property1 { get; set; }
public static string Property2 { get; set; }
public static string Property3 { get; set; }
public static string Property4 { get; set; }
}
Then in code, I might do something like this to save the information:
public void SaveInfo()
{
ArrayList userSetting = new ArrayList();
foreach (Something s in SomeCollectionHere) // For example, a ListView contains the info
{
MyObject o = new MyObject {
Property1 = s.P1;
Property2 = s.P2;
Property3 = s.P3;
Property4 = s.P4;
};
userSetting.Add(o);
}
Properties.Settings.Default.SettingName = userSetting;
}
Now, the code to pull it out is something like this:
public void RestoreInfo()
{
ArrayList setting = Properties.Settings.Default.SettingName;
foreach (object o in setting)
{
MyObject data = (MyObject)o;
// Do something with the data, like load it in a ListView
}
}
I've also made sure to decorate the Settings.Designer.cs file with [global::System.Configuration.SettingsSerializeAs(global::System.Configuration.SettingsSerializeAs.Binary)], like this:
[global::System.Configuration.UserScopedSettingAttribute()]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Configuration.SettingsSerializeAs(global::System.Configuration.SettingsSerializeAs.Binary)]
public global::System.Collections.ArrayList SettingName
{
get {
return ((global::System.Collections.ArrayList)(this["SettingName"]));
}
set {
this["SettingName"] = value;
}
}
Now, randomly, the information will disappear. I can debug this and see that Properties.Settings.Default is returning an empty ArrayList for SettingName. I would really rather not use an ArrayList, but I don't see a way to get a generic collection to store in this way.
I'm about to give up and save this information using plain XML on my own. I just wanted to verify that I was indeed pushing this bit of .NET infrastructure too far. Am I right?
I couldn't find an answer as to why these settings were disappearing and since I kept on happening, I ended up storing the complicated sets of settings separately in an XML file, manually serializing and deserializing them myself.
I had a very similar experience when using the SettingsSerializeAs Binary Attribute in the Settings Designer class. It worked in testing, but some time later it failed to restore the property values.
In my case there had been subsequent additions to the Settings made via the designer. Source control history showed that the SettingsSerializeAs Attribute had been removed from Settings.Designer.cs without my knowledge.
I added the following code to check that the attribute hadn't been accidentally lost it the equivalent of the RestoreInfo() method.
#if(DEBUG)
//Verify that the Property has the required attribute for Binary serialization.
System.Reflection.PropertyInfo binarySerializeProperty = Properties.Settings.Default.GetType().GetProperty("SettingName");
object[] customAttributes = binarySerializeProperty.GetCustomAttributes(typeof(System.Configuration.SettingsSerializeAsAttribute), false);
if (customAttributes.Length != 1)
{
throw new ApplicationException("SettingsSerializeAsAttribute required for SettingName property");
}
#endif
Also, only because it is missing from your example, don't forget to call Save. Say after calling SaveInfo().
Properties.Settings.Default.Save();
When using the Settings feature with the User scope, the settings are saved to the currently logged in user's Application Data (AppData in Vista/7) folder. So if UserA logged in, used your application, and then UserB logged in, he wouldn't have UserA's settings loaded, he would have his own.
In what you're trying to accomplish, I would suggest using the XmlSerializer class for serializing an list of objects. The use is pretty simple:
To serialize it:
ArrayList list = new ArrayList();
XmlSerializer s = new XmlSerializer(typeof(ArrayList));
using (FileStream fs = new FileStream(#"C:\path\to\settings.xml", FileMode.OpenOrCreate))
{
s.Serialize(fs, list);
}
To deserialize it:
ArrayList list;
XmlSerializer s = new XmlSerializer(typeof(ArrayList));
using (FileStream fs = new FileStream(#"C:\path\to\settings.xml", FileMode.Open))
{
list = (ArrayList)s.Deserialize(fs);
}
From your example I don't see anything incorrect about what your trying to do. I think the root of the problem your describing might be your assembly version changing? User settings do not auto-magically upgrade themselves (at least I couldn't get them to).
I can appreciate your plight, I went through this a few months ago. I coded up a UserSettings class that provided standard name/value pair collections (KeyValueConfigurationElement) under a named group heading something like the following:
<configSections>
<section name="userSettings" type="CSharpTest.Net.AppConfig.UserSettingsSection, CSharpTest.Net.Library"/>
</configSections>
<userSettings>
<add key="a" value="b"/>
<sections>
<section name="c">
<add key="a" value="y"/>
</section>
</sections>
</userSettings>
Anyway see if this meets your needs or provides some insight into implementing a custom ConfigurationSection to allow what you need.
Oh yea, the code is here:
http://csharptest.net/browse/src/Library/AppConfig