Trying to use [Description] data annotation attribute with existing code - asp.net-mvc-3

SLIGHT UPDATE BELOW
I am trying to use the [Description] data annotation attribute with enums in order to display a friendly name. I've searched around a lot and cannot get anything implemented. Right now I have code that will display an enum as a string (using an extension), but I am not liking ThisIsAnEnum as an enum name (which is spaced out by the string extension) and it prohibits me from having longer names (which I need to maintain) such as for a radio button item. My goal is to have longer descriptions for radio button items without having to write really long enums. An extension/helper will probably be the right way to go, but I need to "fit" it into the code I am using, which is where I failed using the many examples out there.
The code I am using is generic, in that depending upon some logic either a radio button list, check box list, drop down list, select list or regular text boxes are displayed. For multi-item lists enum's are used, and the enum name is what is displayed (after using the string extension).
Here is the particular code that displays the enum:
public static IEnumerable<SelectListItem> GetItemsFromEnum<T>
(T selectedValue = default(T)) where T : struct
{
return from name in Enum.GetNames(typeof(T))
let enumValue = Convert.ToString((T)Enum.Parse(typeof(T), name, true))
select new SelectListItem
{
Text = name.ProperCase(),
Value = enumValue,
Selected = enumValue.Equals(selectedValue)
};
}
ProperCase is the class that changes the enum to something readable.
I found something that almost worked:
public static string GetEnumDescription<TEnum>(TEnum value)
{
FieldInfo fi = value.GetType().GetField(value.ToString());
DescriptionAttribute[] attributes =
(DescriptionAttribute[])fi.GetCustomAttributes(typeof(DescriptionAttribute), false);
if ((attributes != null) && (attributes.Length > 0))
return attributes[0].Description;
else
return value.ToString();
}
in which case I changed code from Text = name.ProperCase(), to Text = name.GetEnumDescription(...) but if I put value in the parenthesis I get a "does not exist in the current context" message (which I tried fixing but just made the problem worse). If I leave it blank I get the "No overload for ... takes 0 arguments" (again, understandable - but I don't know how to fix). And if I put name in the parenthesis the code compiles but upon viewing the page I get the "Object reference not set..." error on this line:
DescriptionAttribute[] attributes = (DescriptionAttribute[])fi.GetCustomAttributes
(typeof(DescriptionAttribute), false);
I've spent a lot of time on this and know that my stumbling block is the
Text = name.ProperCase(),
code. Any ideas/help? Thanks in advance.
UPDATE:
If I do:
Text = GetEnumDescription(selectedValue),
I actually DO get the [Description] text, however, it just displays for the first enum. So, if I have 5 enums all with different [Description]'s the code just repeats the [Description] for the first enum 5 times instead of displaying differently for each. I hope that makes sense and gets to narrow down the problem.

I'd recommend you the Display attribute:
public static IEnumerable<SelectListItem> GetItemsFromEnum<T>(T selectedValue = default(T)) where T : struct
{
return
from name in Enum.GetNames(typeof(T))
let enumValue = Convert.ToString((T)Enum.Parse(typeof(T), name, true))
select new SelectListItem
{
Text = GetEnumDescription(name, typeof(T)),
Value = enumValue,
Selected = name == selectedValue.ToString()
};
}
public static string GetEnumDescription(string value, Type enumType)
{
var fi = enumType.GetField(value.ToString());
var display = fi
.GetCustomAttributes(typeof(DisplayAttribute), false)
.OfType<DisplayAttribute>()
.FirstOrDefault();
if (display != null)
{
return display.Name;
}
return value;
}
and then you could have:
public enum Foo
{
[Display(Name = "value 1")]
Value1,
Value2,
[Display(Name = "value 3")]
Value3
}
And now you could have:
var foo = Foo.Value2;
var values = GetItemsFromEnum(foo);
Also notice that I have modified the Selected clause in the LINQ expression as yours is not correct.
This being said, personally I would recommend you staying away from enums on your view models as they don't play nicely with what's built-in ASP.NET MVC and you will have to reinvent most of the things.

Related

Why can't my Sitecore custom validator read the item as it has been edited in Content Editor?

I have created a custom validator in Sitecore. It executes correctly, and I can debug it and step through it.
I extended my validator from StandardValidator, which in turn extends from BaseValidator.
In my validator, I get the item:
var item = GetItem();
GetItem is a method from BaseValidator. Based on my reading, this is the standard method to get the item to be validated.
The problem: the item I get back doesn't reflect the values that have been edited in Content Editor. Instead, it appears to simply be the item from the database (so, what it looked like when it first loaded in Content Editor, without reflecting any changes that might have been made to it).
Thus, I cannot validate. To validate the data an editor might have changed in Content Editor, I need to be able to see those changes. Seeing how the item sits in the database right now doesn't help me.
I did some extended debugging, which may or may not be helpful --
I decompiled the kernel, and walked through both StandardValidator and BaseValidator.
The GetItem method from BaseValidator does what I suspect -- it gets the item from the database. But then it runs it through a method called UpdateItem which appears be intended to overlay the inputted values from Content Editor on top of the stored values (thus returning an object that accurately reflects the data which is currently sitting in the editor). This is clearly what I want.
However, UpdateItem only appears to overlay anything if a property called ControlToValidate has a value...and it never does. I've tried this validator as both a Field Validator and an Item Validator. I've initiated it by both saving an item and by tabbing off a field. I put a breakpoint in Visual Studio and a watch on ControlToValidate. One time (inexplicably) it had a value of FIELD165901047 (which corresponded to a field ID in content editor), but in all other cases, it's been null.
What this means is that UpdateItem effectively does nothing, and the item is simply returned as it currently sits in a database, which doesn't help -- I'm trying to validator values entered in Content Editor before saving to the database.
Regardless of my investigation (I think I understand UpdateItem, but I concede that I might be misinterpeting it, and I'm not accounting for potential decompilation errors), I still have this basic problem:
var item = GetItem();
That never seems to return the values from Content Editor. It returns the item directly from the database, which doesn't help me.
In my validator (a field type validator) I've used
var field = this.GetField();
to get new value of the field to validate. The same should work for a field validator. It's probably a bit different for an item validator but I've never written an item validator.
It's also a method of the BaseValidator and returns a value of the type Field. You can assign this directly to the type of the target field you want, e.g. HtmlField (Sitecore has implemented the casting operators so you can do this):
HtmlField htmlField = field;
I managed to create a work-around for this, and it seems to be working for me. I'm not that happy that I don't know why controlToValidate never has a value, but my workaround manages to get the new value that hasn't yet been saved.
Basically, I've overridden the code that runs GetItem(), and I find the new value in the page's SaveArgs as shown below. The magic happens on the line that has:
field.Value = ((((SaveArgs) ((ClientPage) page)?.CurrentPipelineArgs)?.Items[0].Fields) ?? Array.Empty<SaveArgs.SaveField>()).FirstOrDefault(x => x.ID == field.ID)?.Value ?? field.Value;
protected override Item GetItem()
{
var obj = base.GetItem();
if (obj == null || obj.Versions.Count == 0)
return null;
UpdateItem(obj);
return obj;
}
private void UpdateItem(Item item)
{
Assert.ArgumentNotNull(item, nameof(item));
using (new SecurityDisabler())
item.Editing.BeginEdit();
var page = (Page)null;
var current = HttpContext.Current;
if (current != null)
page = current.Handler as Page;
foreach (Field field in item.Fields)
{
var controlToValidate = ControlToValidate;
if (string.IsNullOrEmpty(controlToValidate) || field.ID != FieldID)
{
field.Value = ((((SaveArgs) ((ClientPage) page)?
.CurrentPipelineArgs)?.Items[0].Fields) ?? Array.Empty<SaveArgs.SaveField>()).FirstOrDefault(x => x.ID == field.ID)
?.Value ?? field.Value;
}
else
{
var str = current != null ? RuntimeValidationValues.Current[controlToValidate] : null;
if (page != null && str != null)
{
var control = page.FindControl(controlToValidate);
if (control != null)
{
if (control is IContentField contentField)
str = contentField.GetValue();
else if (ReflectionUtil.GetAttribute(control, typeof(ValidationPropertyAttribute)) is ValidationPropertyAttribute attribute)
str = ReflectionUtil.GetProperty(control, attribute.Name) as string;
}
}
if (str == null && current != null)
str = current.Request.Form[controlToValidate];
if (str != null && str != "__#!$No value$!#__")
field.Value = str;
}
}
}

isolated storage in windows phone developement

Im using VS to develop a windows phone app. Im doing it wp8 but it doesnt matter because it the code works for 7 too. Anyway, I have a text box and a button. When the text from the text box is entered, and the button is clicked it adds that to isolated storage.
On my other page, I have a textblock. Which should display what I wrote in the text box. It does work, but first let me sho you my code.
if (appsettings.Contains("name"))
{
appsettings.Remove("name");
appsettings.Add("name", TitleTextBox.Text); //rename if already exists
}
and then the second page that collects the info is below.
if (appsettings.Contains("name"))
{
string content = appsettings["name"].ToString(); //converts to string
titleTextBlock.Text = content; //shows title in text block
}
The problem is, the "name" works. However, if I call it ANYTHING else it does not. I want to add a different name because i want to be able to input two lots. For example two text box's and then when you press the button and go to the other page, it has two textblocks displaying each string in each one. I can't seem to do this because only "name" works. Ive changed it to other names and it doesnt work. Does anyone know why?
IsolatedStorageSettings works as a Dictionary. If you want to acces a specific key it should exist in the Dictionary.
If you try to change the value that already exists you can do like this:
if (appSettings.Contains("key")) appSettings["key"] = "new value";
else appSettings.Add("key", "new value");
Don't also forget to save your appSettings:
appSettings.Save();
And also according to your code - in ISS you can put not only string - it can be any object, if you want to get it, you should make a cast or use as:
string content = (string)appsettings["name"]; //converts to string
string content = appsettings["name"] as string;
EDIT - after comments, rebuild once more
If you want to have a to-do-list and you know that every task has its specific title, description and time then I would advise to create a special class for this, for example:
public class myTodo
{
public string TaskTitle { get; set; }
public string TaskDescription { get; set; }
public TimeSpan ElapsedTime { get; set; }
}
I used TimeSpan because I think it's easier to manage Time with it. Then if you want to Save/Load your myTodo you can do like this:
// create an example of your task
myTodo newTask = new myTodo() { TaskTitle = "Clean", TaskDescription = "Clean room", ElapsedTime = new TimeSpan(2, 0, 0) };
// add it to ISS and save
if (appSettings.Contains("firatTask")) appSettings["firatTask"] = newTask;
else appSettings.Add("firatTask", newTask);
appSettings.Save();
// try to load
myTodo read = appSettings["firatTask"] as myTodo;
You can access your item like this:
read.Title = TitleTextBox.Text; // and so on
Consider also making a List<myToDo> and be aware that ISS shoul also handle this:
List<myTodo> listJob = new List<myTodo>();
listJob.Add(firstTask); // firstTask is myToDo
listJob.Add(secondTask); // secondTask is myToDo
if (appSettings.Contains("listTask")) appSettings["listTask"] = listJob;
else appSettings.Add("listTask", listJob);
appSettings.Save();
List<myTodo> readList = appSettings["listTask"] as List<myTodo>;

Sitecore 6 WFFM: ListField value?

I am building a complex WFFM user control that extends BaseUserControl. This control has multiple fields that get prepopulated based on some business logic. One of the fields is supposed to be a drop down which shows values from a series of Sitecore items. Here is the definition of my ListField property:
private string myListField;
[VisualProperty("My List Field:", 100),
VisualCategory("Appearance"), VisualFieldType(typeof(ListField))]
public string MyListField{
get { return myListField; }
set { myListField= value; }
}
When I debug this, the content of titleFieldList is a string that contains the following XML in URL encoded format:
%3Cquery%20t%3D%22root%22%20vf%3D%22__ID%22%20tf%3D%22Value%22%3E%3Cvalue%3E%7B814FC177-2750-48D6-B7B7-4EE87012C637%7D%3C%2Fvalue%3E%3C%2Fquery%3E
which, decode, is:
<query t="root" vf="__ID" tf="Value">
<value>{814FC177-2750-48D6-B7B7-4EE87012C637}</value>
</query>
I understand the meaning of this XML. It says that all the children of the item whose ID is that Guid are supposed to be used to populate my list, using the template field "__ID" for the value and the template field "value" for the text.
Can someone help me understand what am I supposed to do to bind an asp:DropDownList to this? Is this a particular sitecore object that has been serialized and encoded?
Is there an sc:control that can handle this?
Thanks!
** EDIT **
So I tried the following piece of code
string encodedQuery = TitleFieldList;
string query = HttpUtility.UrlDecode(encodedQuery);
XDocument xmlQuery = XDocument.Parse(query);
if (xmlQuery.Element("query") != null)
{
Dictionary<string, string> nodesDictionary = new Dictionary<string, string>();
string root = xmlQuery.Element("query").Element("value").Value;
string value = xmlQuery.Element("query").Attribute("vf").Value;
string text = xmlQuery.Element("query").Attribute("tf").Value;
Item rootItem = SitecoreUtility.GetItemWithoutSecurity(new ID(root));
ChildList childList = rootItem.GetChildren();
foreach (Item child in childList)
{
string theValue = (value == "__ID") ? child.ID.ToString() : child.Fields[value].ToString();
string theText = child.Fields[text].ToString();
nodesDictionary.Add(theText, theValue);
}
titleDropDownList.DataSource = nodesDictionary;
titleDropDownList.DataTextField = "key";
titleDropDownList.DataValueField = "value";
titleDropDownList.DataBind();
}
and it works. The dropdownlist is populated with the correct data coming from the fields that were selected in the editor. I just can't believe that there is no easier way to do this. Plus how am I supposed to honor the MultipleSelectedValueField and the EmptyChoiceField if present?
Try to change the return type of your property and add attribute TypeConverter. The type specified in TypeConverter is responsible for converting raw string value to a property's return type.
ListItemCollectionConverter - is a converter provided by WFFM
[VisualProperty("My List Field:", 100)]
[VisualCategory("Appearance")]
[VisualFieldType(typeof(ListField))]
[TypeConverter(typeof(Sitecore.Form.Web.UI.Controls.ListItemCollectionConverter.ListItemCollectionConverter))]
public Sitecore.Form.Web.UI.Controls.ListItemCollection MyListField{
get { return myListField; }
set { myListField= value; }
}

Conditional Calucations with MVC4

I am trying to conditionally calculate some values based on user inputs in a form. The "inputs" themselves tie into the calculation, in that if a user is presented with two radio button lists their selections will determine whether or not a currency amount is attached, and then later added together. I don't understand jquery, and so I cannot figure out how to use something like knockout.js to do what I want (besides, my calculation will be served up in a confirmation view only and will not be "re-calculated" unless the user goes back and changes their selections).
Note, I am not talking about the scenario where you have a textbox that accepts a value and adding it to another textbox value the user inputs (e.g., user enters 10, then 10, then form calculates 20). The values are static based upon selection.
For context, I am using Serializer and a wizard which is stepping through and passing model selections/inputs to the subsequent view.
I can do something like this as an example (it's simple, but based on responses I can figure out how to build it up to the more complex scenario I have):
Model.cs:
[DataType(DataType.Currency)]
[DisplayFormat(DataFormatString = "{0:c}")]
public decimal calculated { get; set; }
[DataType(DataType.Currency)]
[DisplayFormat(DataFormatString = "{0:c}")]
public decimal option1 = 500;
[DataType(DataType.Currency)]
[DisplayFormat(DataFormatString = "{0:c}")]
public decimal option2 = 100;
//Following is just my idea to calculate $0 if
//a selection results in a "no value option"
//although I suppose if nothing is selected I
//can just avoid calculating + $0
[DataType(DataType.Currency)]
[DisplayFormat(DataFormatString = "{0:c}")]
public decimal nooption = 0;
Then in Confirm.cshtml:
#{
Model.calculated = Model.option1 + Model.option2 + Model.nooption;
}
....
#Html.DisplayFor(m => m.calculated)
will display $600.00. That's easy enough.
However, my goal is threefold. First, I need to tie in, for example, option1 with the selection from a radiobutton list, and option2 with another radiobutton list depending on what the user selects. Second, depending on the selection I want to conditionally calculate all the options present which will obviously vary based on user selection (some users will select option1 and option2, others just option1, others just option2, still others neither). Third, where is the best place to have the calculation take place (the view, a .cs file, the controller (although I am trying to minimize code there), etc.).
So, if I have these enums for the radiobutton lists (each option corresponding to an option for now, later I will discuss two options per radiobutton list enum):
public enum radio1
{
radio1selection1, // this would correspond to nooption (or $0)
radio1selection2 // this would correspond to option1 (or $500)
}
public enum radio2
{
radio2selection1, // this would correspond to nooption (or $0)
radio2selection2 // this would correspond to option2 (or $100)
}
I've indicated in the enums how I'd like to "tie" the option to the selection.
My problem is in trying to do if statements, which I cannot figure out properly. Something like this in Confirm.cshtml:
#{
if (Model.radio1 == radio1selection1)
Model.calculated = Model.nooption;
else....
}
The above is completely wrong, I know. I am trying to think of all the permutations of calculating, but I don't think its worth the effort because it would either be wrong or it would work but be too long when there is an easier way. I was even thinking of trying case/break but that too seems oddly the wrong approach.
What's adding to the complexity for me is if my enums have more than two selections, and those other selections result in several options, say option1 and option1a, and depending on which the user selects will determine the calculation. So the enum becomes:
public enum radio1
{
radio1selection1, // this would correspond to nooption (or $0)
radio1selection2, // this would correspond to option1 (or $500)
radio1selection3 // this would correspond to NEW
// option1a (or $750) (with a corresponding
// "int" named "option1a")
}
One of the things I thought of doing was to just display all the options selected in a <table> in the view, so that even if something is a $0 value, it would just say $0 as the option. Then I thought I could just add all the values present. But still I don't know the best way to accomplish that.
Any opinions on how I would be able to and or all three of my stated goals above?
I would define the property as follows:
[DataType(DataType.Currency)]
[DisplayFormat(DataFormatString = "{0:c}")]
public decimal calculated { get { return option1 + option2 + option3; } }
You can make the logic as conditional as necessary inside the getter. I think model is the best place for those calculations, which is what we get with the calculated property.
To render a collection of options, define them as follows:
public class MyOption
{
public string ID { get; set; }
public string Name { get; set; }
}
List<MyOptions> options = new List<MyOptions>();
options.Add(new MyOption { ID = "1", Name = "First" });
model.Options = options;
You can have a method instead of the Options property that returns a list of options based on the data of the option* properties.
Then convert it to a SelectList object in some helper method. An HTML helper can be defined as follows:
public static class MyHelperExtensions
{
public static SelectList MyOptionList(this HtmlHelper helper, List<MyOptions> options)
{
return new SelectList(options, "ID", "Name");
}
}
In the view, use the following code to render the options:
#Html.DropDownListFor(x => x.MyOptionProperty, Html.MyOptionList(Model.Options))
I figured out how to do this (might not be clean/efficient/standard practice, but it got the job done):
Model.calculated =
Model.PriceQuote.priceChapter7Solution +
((Model.radiobuttonlist1 ==
Namespace.ViewModels.MyModel.radio1.radio1selection1) ?
Model.option1 :
(Model.radiobuttonlist1 ==
Namespace.ViewModels.MyModel.radio1.radio1selection2) ?
Model.option2 :
Model.nooption);
I could just add + after ... : Model.nooption) instead of ending the statement with ; to calculate conditions on other options.
Originally I did not know that the conditional (? :) operator could contain more than two expressions - i.e., it's right associative so additional conditions will be evaluated as a ? b : (c ? d : e). See Docs.

Using DataObjectTypeName in DataObjectSource

The functionality I am trying to use is:
- Create a ObjectDataSource for selection and updating controls on a web page (User Control).
- Use the DataObjectTypeName to have an object created that would send the data to an UpdateMethod.
- Before the values are populated in the DataObjectTypeName’s object, I would like to pre-populate the object so the unused items in the class are not defaulted to zeros and empty strings without me knowing whether the zero or default string was set by the user or by the application.
I cannot find a way to pre-populate the values (this was an issue back in 2006 with framework 2.0). One might ask “Why would anyone need to pre-populate the object?”. The simple answer is: I want to be able to randomly place controls on different User Controls and not have to be concerned with which UpdateMethod needs to handle which fields of an object.
For Example, let’s say I have a class (that reflects a SQL Table) that includes the fields: FirstName, LastName, Address, City, State, Zip. I may want to give the user the option to change the FirstName and LastName and not even see the Address, City, State, Zip (or vice-versa). I do not want to create two UpdateMethods where one handled FirstName and LastName and the other method handles the other fields. I am working with a Class of some 40+ columns from multiple tables and I may want some fields on one screen and not another and decide later to change those fields from one screen to another (which breaks my UpdateMethods without me knowing).
I hope I explained my issue well enough.
Thanks
This is hardly a solution to the problem, but it's my best stab at it.
I have a GridView with its DataSourceID set to an ObjectDataSource.
Whenever a row is updated, I want the property values in the object to be selectively updated - that is - only updated if they appear as columns in the GridView.
I've created the following extension:
public static class GridViewExtensions
{
public static void EnableLimitUpdateToGridViewColumns(this GridView gridView)
{
_gridView = gridView;
if (_gridView.DataSourceObject != null)
{
((ObjectDataSource)_gridView.DataSourceObject)
.Updating += new ObjectDataSourceMethodEventHandler(objectDataSource_Updating);
}
}
private static GridView _gridView;
private static void objectDataSource_Updating(object sender, ObjectDataSourceMethodEventArgs e)
{
var newObject = ((object)e.InputParameters[0]);
var oldObjects = ((ObjectDataSource)_gridView.DataSourceObject).Select().Cast<object>();
Type type = oldObjects.First().GetType();
object oldObject = null;
foreach (var obj in oldObjects)
{
if (type.GetProperty(_gridView.DataKeyNames.First()).GetValue(obj, null).ToString() ==
type.GetProperty(_gridView.DataKeyNames.First()).GetValue(newObject, null).ToString())
{
oldObject = obj;
break;
}
}
if (oldObject == null) return;
var dynamicColumns = _gridView.Columns.OfType<DynamicField>();
foreach (var property in type.GetProperties())
{
if (dynamicColumns.Where(c => c.DataField == property.Name).Count() == 0)
{
property.SetValue(newObject, property.GetValue(oldObject, null), null);
}
}
}
}
And in the Page_Init event of my page, I apply it to the GridView, like so:
protected void Page_Init()
{
GridView1.EnableLimitUpdateToGridViewColumns();
}
This is working well for me at the moment.
You could probably apply similar logic to other controls, e.g. ListView or DetailsView.
I'm currently scratching my head to think of a way this can be done in a rendering-agnostic manner - i.e. without having to know about the rendering control being used.
I hope this ends up as a normal feature of the GridView or ObjectDataSource control rather than having to hack it.

Resources