I've got a repeater looping through a list of objects that are of different types. I'd like to render the objects differently depending on their type. For this I need some kind of control (since I want to avoid using the code-behind) that has a behavior similar to a switch/case statement. Basically it could look like this:
<xxx:TestType Object='<%# Container.DataItem %>'>
<Case Type="Namespace.ClassX">
<asp:Label ... />
</Case>
<Case Type="Namespace.ClassY">
<asp:TextBox ... />
</Case>
<Default>
<p>Other</p>
</Default>
</xxx:TestType>
I've made web controls before, but this is a rather complex one...
How do I make it support multiple <Case> elements?
Is it possible to implement the <Case Type="..."> elements, or am I limited to attribute-less elements?
I'm guessing I have to make a type for the <Case> elements and somehow specifying it for the main web control?
I'd be grateful for any pointers, links to tutorials, etc!
Alternative
Alternatively, suggest a nicer way of rendering different HTML/ASP.NET controls based on the type of the currently bound object. The first method that popped into my head was this, which I consider (very) ugly:
<asp:PlaceHolder Visible='<%# CheckType(Container, typeof(ClassX)) %>' runat="server">
...
</asp:PlaceHolder>
<asp:PlaceHolder Visible='<%# CheckType(Container, typeof(ClassY)) %>' runat="server">
...
</asp:PlaceHolder>
The MultiView control is the closest thing out-of-the-box in ASP.NET to a C# switch statement.
Look at implementing ITemplate interface
public class Case
{
[PersistenceMode(PersistenceMode.Attribute)]
public string Type { get; set; }
[PersistenceMode(PersistenceMode.InnerDefaultProperty)]
public ITemplate Template { get; set; }
}
public class DeclarativeCase
: CompositeControl
{
[PersistenceMode(PersistenceMode.InnerProperty)]
public List<Case> Cases { get; set; }
[PersistenceMode(PersistenceMode.InnerProperty)]
public ITemplate Default { get; set; }
}
<xxx:DeclarativeCase runat="server" ID="test">
<Cases>
<xxx:Case Type="Namespace.TypeName">
<Template>
<asp:Label ID="Label1" runat="server"></asp:Label>
</Template>
</xxx:Case>
</Cases>
<Default>
<asp:Label ID="Label2" runat="server"></asp:Label>
</Default>
</xxx:DeclarativeCase>
Why don't you ditch the server control and use the C# switch statement. All you need is to wrap it in the code tags.
Another approach is a iteration (for loop). Create an interface with a type property and a render method. Then iterate over the interfaces until you've found the correct type.
I had a similar requirement a while ago and I did something similar to:
[ParseChildren(true, "Templates")]
public class TypedRepeater : CompositeDataBoundControl
{
[
PersistenceMode(PersistenceMode.InnerProperty),
Browsable(false),
MergableProperty(false)
]
public TypedTemplateCollection Templates
{
get;
}
protected TypedTemplate GetTemplate(object dataItem)
{
if (dataItem == null)
{
return null;
}
foreach (TypedTemplate template in Templates)
{
Type itemType = dataItem.GetType();
if (dataItem.IsAssignableFrom(template.Type))
{
return template;
}
}
return null;
}
protected TypedTemplateRepeaterItem CreateItem(int index, object dataItem, bool dataBinding)
{
TypedTemplateRepeaterItem repeaterItem = new TypedTemplateRepeaterItem();
if ((!dataBinding) && (ViewState[string.Format("TemplateIxc_{0}", index)] is int))
{
int _template = (int)ViewState[string.Format("TemplateIxc_{0}", index)];
if ((_template >= 0) && (_template < Templates.Count) && (Templates[_template].ItemTemplate != null))
{
Templates[_template].ItemTemplate.InstantiateIn(repeaterItem);
}
else
{
DefaultTemplate.InstantiateIn(repeaterItem);
}
}
else if (dataBinding)
{
TypedTemplate template = GetTemplate(dataItem);
ITemplate itemTemplate = DefaultTemplate;
if (template != null)
{
itemTemplate = template.ItemTemplate;
ViewState[string.Format("TemplateIxc_{0}", index)] = Templates.IndexOf(template);
}
else
{
ViewState[string.Format("TemplateIxc_{0}", index)] = -1;
}
repeaterItem.DataItem = dataItem;
repeaterItem.DataItemIndex =
repeaterItem.DisplayIndex = index;
itemTemplate.InstantiateIn(repeaterItem);
repeaterItem.DataBind();
repeaterItem.DataItem = null;
}
return repeaterItem;
}
protected override int CreateChildControls(IEnumerable dataSource, bool dataBinding)
{
int count = 0;
if (dataSource != null)
{
foreach (object dataItem in dataSource)
{
TypedTemplateRepeaterItem repeaterItem = CreateItem(count, dataItem, dataBinding);
Controls.Add(repeaterItem);
count++;
}
}
return count;
}
}
where TypedTemplateCollection is a StateManagedCollection of a TypedTemplate class:
[ParseChildren(true, "ItemTemplate")]
public class TypedTemplate
{
public Type Type
{
get { return Type.GetType(TypeName); }
}
[
PersistenceMode(PersistenceMode.Attribute),
Browsable(true),
DefaultValue("")
]
public string TypeName
{
get;
set;
}
[
PersistenceMode(PersistenceMode.InnerProperty),
Browsable(true),
DefaultValue(typeof(ITemplate), null),
TemplateContainer(typeof(TypedTemplateRepeaterItem))
]
public ITemplate ItemTemplate
{
get;
set;
}
}
and TypedTemplateRepeaterItem is:
public class TypedTemplateRepeaterItem : WebControl, INamingContainer, IDataItemContainer
{
#region IDataItemContainer Members
public object DataItem
{
get;
set;
}
public int DataItemIndex
{
get;
set;
}
public int DisplayIndex
{
get;
set;
}
#endregion
}
The MSDN site has a simple clear example of templating and it certainly seems like the way to go in your case.
Related
I've created a custom Image Field Template which works fine. I decided to create some kind of validation for image sizes so I've created a custom validation attribute. here is the code:
public class ImageSizeValidator : ValidationAttribute
{
public long MaxSize { get; set; }
public long MinSize { get; set; }
public override bool IsValid(object value)
{
if (value == null)
return true;
byte[] image = (byte[]) value;
if (image.Length / 1024L > MaxSize || image.Length / 1024L < MinSize)
return false;
return true;
}
}
and here is my FieldTemplate Image_Edit.ascx:
<%# Control Language="C#" AutoEventWireup="true" CodeBehind="Image_Edit.ascx.cs" Inherits="RastinArgham.CRM.Web.DynamicData.FieldTemplates.Image_Edit" %>
<asp:FileUpload ID="FileUpload1" runat="server" OnDataBinding="FileUpload1DataBinding" />
<asp:RequiredFieldValidator runat="server" ID="RequiredFieldValidator1" CssClass="DDControl DDValidator" ControlToValidate="FileUpload1" Display="Static" Enabled="false" />
<asp:DynamicValidator runat="server" ID="DynamicValidator1" CssClass="DDControl DDValidator" ControlToValidate="FileUpload1" Display="Static" />
and here is Image_Edit.ascx.cs:
public partial class Image_Edit : System.Web.DynamicData.FieldTemplateUserControl
{
protected void Page_Load(object sender, EventArgs e)
{
SetUpValidator(RequiredFieldValidator1);
SetUpValidator(DynamicValidator1);
}
protected override void ExtractValues(IOrderedDictionary dictionary)
{
if (FileUpload1.PostedFile == null || String.IsNullOrEmpty(FileUpload1.PostedFile.FileName) || FileUpload1.PostedFile.InputStream.Length == 0)
return;
dictionary[Column.Name] = FileUpload1.FileBytes;
}
public override Control DataControl
{
get
{
return FileUpload1;
}
}
}
and finally my entity meta data class:
[Display(Name = "ServicePhoto", Order = 410, GroupName = "Misc")]
[HideColumnIn(PageTemplate.List)]
[UIHint("Image")]
[ImageSizeValidator(ErrorMessage = "Invalid Photo Size", MinSize = 30, MaxSize = 300)]
public object ServicePhoto { get; set; }
There are two problems:
1- when IsValid(object value) called value is always null!
2- when trying to upload a new image I always get "The value is not valid" Error on client side of
validation.
The following code works for webform view engine.
<% Model.Categories.ForEach(x => { %>
<li>#x.Name</li>
<% }) %>
I wrote the above code as below in razor view:
#Model.Categories.ForEach(x => {
<li>#x.Name</li>
})
But this doesn't work.
Can anyone suggest, Is there any way to achieve this in razor view?
Thanks in advance.
Is there any reason you need to do that?
#foreach(var x in Model.Categories) {
<li>#x.Name</li>
}
Above does the exact same thing, and is more idiomatic.
I can't see a way to output the .ForEach() delegate result using the Razor syntax. Razor expects called methods or invoked properties to return a value, which is then emitted into the view output. Because .ForEach() doesn't return anything, it doesn't know what to do with it:
Cannot explicitly convert type 'void' to 'object'
You can have the iterator index quite tersely like so:
#foreach (var item in Model.Categories.Select((cat, i) => new { Item = cat, Index = i })) {
<li>#x.Index - #x.Item.Name</li>
}
If you want to define this as an extension method, instead of an anonymous type, you can create a class to hold the Item, Index pair, and define an extension method on IEnumerable<T> which yields the items in the original enumerable wrapped in this construct.
public static IEnumerable<IndexedItem<T>> WithIndex<T>(this IEnumerable<T> input)
{
int i = 0;
foreach(T item in input)
yield return new IndexedItem<T> { Index = i++, Item = item };
}
The class:
public class IndexedItem<T>
{
public int Index { get; set; }
public T Item { get; set; }
}
Usage:
#foreach(var x in Model.Categories.WithIndex()) {
<li>#x.Index - #x.Item.Name</li>
}
Thanks for your help. I found how to implement delegates in Razor based on the following article by Phil Haack.
Templated Razor Delegates
Here is the extension code for IEnumerable:
public static HelperResult ForEachTemplate<TEntity>(
this IEnumerable<TEntity> items,
Func<RowEntity<TEntity>, HelperResult> template)
{
return new HelperResult(writer =>
{
var index = 0;
foreach (var item in items)
{
template(new RowEntity<TEntity> {
Index = index,
Value = item }).WriteTo(writer);
index++;
}
});
}
public class RowEntity<TEntity>
{
public int Index { get; set; }
public TEntity Value { get; set; }
}
View Model is :
// IEnumerable<Person>
public class Person
{
public string Name { get; set; }
}
And the use of that extension methods:
#Model.ForEachTemplate(#<p>Index: #item.Index Name: #Item.Value.Name</p>)
For example:
<uc:AdmiralAckbar runat="server" id="myCustomControl">
<Warning SomeAttribute="It's A Trap">
My Data
</Warning>
</uc:AdmiralAckbar>
I'm not sure how to add SomeAttribute. Any ideas?
Code without the attribute is:
private ITemplate warning = null;
[TemplateContainer(typeof(INamingContainer))]
[PersistenceMode(PersistenceMode.InnerProperty)]
public ITemplate Warning
{
get
{
return warning;
}
set
{
warning = value;
}
}
The answer is yes.
For this you should create a type which implements ITemplate interface and add a custom property/properties there (I added property Name in my example); also add a class which inherits from Collection<YourTemplate>.
Here is an example of doing that:
public class TemplateList : Collection<TemplateItem> { }
public class TemplateItem : ITemplate
{
public string Name { get; set; }
public void InstantiateIn(Control container)
{
var div = new HtmlGenericControl("div");
div.InnerText = this.Name;
container.Controls.Add(div);
}
}
and a control itself:
[ParseChildren(true, "Templates"), PersistChildren(false)]
public class TemplateLibrary : Control
{
public TemplateLibrary()
{
Templates = new TemplateList();
}
[PersistenceMode(PersistenceMode.InnerProperty)]
public TemplateList Templates { get; set; }
protected override void RenderChildren(HtmlTextWriter writer)
{
foreach (var item in Templates)
{
item.InstantiateIn(this);
}
base.RenderChildren(writer);
}
}
and finally an example of usage:
<my:TemplateLibrary runat="server">
<my:TemplateItem Name="hello" />
<my:TemplateItem Name="there" />
</my:TemplateLibrary>
BTW, you could also use it as:
<my:TemplateLibrary runat="server">
<Templates>
<my:TemplateItem Name="hello" />
<my:TemplateItem Name="there" />
</Templates>
</my:TemplateLibrary>
the effect will be the same.
I've spent the majority of the past week knee deep in the new templating functionality baked into MVC2. I had a hard time trying to get a DropDownList template working. The biggest problem I've been working to solve is how to get the source data for the drop down list to the template. I saw a lot of examples where you can put the source data in the ViewData dictionary (ViewData["DropDownSourceValuesKey"]) then retrieve them in the template itself (var sourceValues = ViewData["DropDownSourceValuesKey"];) This works, but I did not like having a silly string as the lynch pin for making this work.
Below is an approach I've come up with and wanted to get opinions on this approach:
here are my design goals:
The view model should contain the source data for the drop down list
Limit Silly Strings
Not use ViewData dictionary
Controller is responsible for filling the property with the source data for the drop down list
Here's my View Model:
public class CustomerViewModel
{
[ScaffoldColumn(false)]
public String CustomerCode{ get; set; }
[UIHint("DropDownList")]
[DropDownList(DropDownListTargetProperty = "CustomerCode"]
[DisplayName("Customer Code")]
public IEnumerable<SelectListItem> CustomerCodeList { get; set; }
public String FirstName { get; set; }
public String LastName { get; set; }
public String PhoneNumber { get; set; }
public String Address1 { get; set; }
public String Address2 { get; set; }
public String City { get; set; }
public String State { get; set; }
public String Zip { get; set; }
}
My View Model has a CustomerCode property which is a value that the user selects from a list of values. I have a CustomerCodeList property that is a list of possible CustomerCode values and is the source for a drop down list. I've created a DropDownList attribute with a DropDownListTargetProperty. DropDownListTargetProperty points to the property which will be populated based on the user selection from the generated drop down (in this case, the CustomerCode property).
Notice that the CustomerCode property has [ScaffoldColumn(false)] which forces the generator to skip the field in the generated output.
My DropDownList.ascx file will generate a dropdown list form element with the source data from the CustomerCodeList property. The generated dropdown list will use the value of the DropDownListTargetProperty from the DropDownList attribute as the Id and the Name attributes of the Select form element. So the generated code will look like this:
<select id="CustomerCode" name="CustomerCode">
<option>...
</select>
This works out great because when the form is submitted, MVC will populate the target property with the selected value from the drop down list because the name of the generated dropdown list IS the target property. I kinda visualize it as the CustomerCodeList property is an extension of sorts of the CustomerCode property. I've coupled the source data to the property.
Here's my code for the controller:
public ActionResult Create()
{
//retrieve CustomerCodes from a datasource of your choosing
List<CustomerCode> customerCodeList = modelService.GetCustomerCodeList();
CustomerViewModel viewModel= new CustomerViewModel();
viewModel.CustomerCodeList = customerCodeList.Select(s => new SelectListItem() { Text = s.CustomerCode, Value = s.CustomerCode, Selected = (s.CustomerCode == viewModel.CustomerCode) }).AsEnumerable();
return View(viewModel);
}
Here's my code for the DropDownListAttribute:
namespace AutoForm.Attributes
{
public class DropDownListAttribute : Attribute
{
public String DropDownListTargetProperty { get; set; }
}
}
Here's my code for the template (DropDownList.ascx):
<%# Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<IEnumerable<SelectListItem>>" %>
<%# Import Namespace="AutoForm.Attributes"%>
<script runat="server">
DropDownListAttribute GetDropDownListAttribute()
{
var dropDownListAttribute = new DropDownListAttribute();
if (ViewData.ModelMetadata.AdditionalValues.ContainsKey("DropDownListAttribute"))
{
dropDownListAttribute = (DropDownListAttribute)ViewData.ModelMetadata.AdditionalValues["DropDownListAttribute"];
}
return dropDownListAttribute;
}
</script>
<% DropDownListAttribute attribute = GetDropDownListAttribute();%>
<select id="<%= attribute.DropDownListTargetProperty %>" name="<%= attribute.DropDownListTargetProperty %>">
<% foreach(SelectListItem item in ViewData.Model)
{%>
<% if (item.Selected == true) {%>
<option value="<%= item.Value %>" selected="true"><%= item.Text %></option>
<% } %>
<% else {%>
<option value="<%= item.Value %>"><%= item.Text %></option>
<% } %>
<% } %>
</select>
I tried using the Html.DropDownList helper, but it would not allow me to change the Id and Name attributes of the generated Select element.
NOTE: you have to override the CreateMetadata method of the DataAnnotationsModelMetadataProvider for the DropDownListAttribute. Here's the code for that:
public class MetadataProvider : DataAnnotationsModelMetadataProvider
{
protected override ModelMetadata CreateMetadata(IEnumerable<Attribute> attributes, Type containerType, Func<object> modelAccessor, Type modelType, string propertyName)
{
var metadata = base.CreateMetadata(attributes, containerType, modelAccessor, modelType, propertyName);
var additionalValues = attributes.OfType<DropDownListAttribute>().FirstOrDefault();
if (additionalValues != null)
{
metadata.AdditionalValues.Add("DropDownListAttribute", additionalValues);
}
return metadata;
}
}
Then you have to make a call to the new MetadataProvider in Application_Start of Global.asax.cs:
protected void Application_Start()
{
RegisterRoutes(RouteTable.Routes);
ModelMetadataProviders.Current = new MetadataProvider();
}
Well, I hope this makes sense and I hope this approach may save you some time. I'd like some feedback on this approach please. Is there a better approach?
I think I found a solution to make it work when using Html.EditorForModel(); When using EditorForModel(), MVC uses Object.ascx to loop through all properties of the model and calls the corresponding template for each property in the model. ASP.Net MVC out of the box has Object.ascx in code, but you can create your own Object.ascx. Just create an EditorTemplates subfolder in your Shared View folder. Create an Object.ascx file there. (read this post for more information: http://bradwilson.typepad.com/blog/2009/10/aspnet-mvc-2-templates-part-3-default-templates.html)
Here's my Object.ascx:
<%# Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl" %>
<%# Import Namespace="WebAppSolutions.Helpers" %>
<% if (ViewData.TemplateInfo.TemplateDepth > 1) { %>
<%= ViewData.ModelMetadata.SimpleDisplayText%>
<% }
else { %>
<% foreach (var prop in ViewData.ModelMetadata.Properties.Where(pm => pm.ShowForEdit && !ViewData.TemplateInfo.Visited(pm))) { %>
<% var htmlFieldName = Html.HtmlFieldNameFor(prop.PropertyName);%>
<% if (prop.HideSurroundingHtml) { %>
<%= Html.Editor(htmlFieldName)%>
<% }
else { %>
<div id="<%= htmlFieldName %>Container" class="editor-field">
<% if (!String.IsNullOrEmpty(Html.Label(prop.PropertyName).ToHtmlString())) { %>
<%= Html.Label(prop.PropertyName, Html.HtmlDisplayName(prop.PropertyName), prop.IsRequired)%>
<% } %>
<%= Html.Editor(prop.PropertyName, "", htmlFieldName)%>
<%= Html.ValidationMessage(prop.PropertyName, "*") %>
</div>
<% } %>
<% } %>
<% } %>
I have some custome code in my WebAppSolutions.Helpers for HtmlFieldNameFor and HtmlDisplayName. These helpers retrieve data from attributes applied to properties in the view model.
public static String HtmlFieldNameFor<TModel>(this HtmlHelper<TModel> html, String propertyName)
{
ModelMetadata modelMetaData = GetModelMetaData(html, propertyName);
return GetHtmlFieldName(modelMetaData, propertyName);
}
public static String HtmlDisplayName<TModel>(this HtmlHelper<TModel> html, String propertyName)
{
ModelMetadata modelMetaData = GetModelMetaData(html, propertyName);
return modelMetaData.DisplayName ?? propertyName;
}
private static ModelMetadata GetModelMetaData<TModel>(HtmlHelper<TModel> html, String propertyName)
{
ModelMetadata modelMetaData = ModelMetadata.FromStringExpression(propertyName, html.ViewData);
return modelMetaData;
}
private static String GetHtmlFieldName(ModelMetadata modelMetaData, string defaultHtmlFieldName)
{
PropertyExtendedMetaDataAttribute propertyExtendedMetaDataAttribute = GetPropertyExtendedMetaDataAttribute(modelMetaData);
return propertyExtendedMetaDataAttribute.HtmlFieldName ?? defaultHtmlFieldName;
}
The key to getting this to work using EditorModelFor() is this (should be line 20 or so in Object.ascx above):
<%= Html.Editor(prop.PropertyName, "", htmlFieldName)%>
prop.PropertyName is the property in the ViewModel containing the list of data that will become the DropDownList. htmlFieldName is the name of the property that's hidden that the DropDownList property is replacing. Make sense?
I hope this helps you.
Perfect. This is what I'm looking for. Thanks!
But your example model is simple model. How about a complex viewmodel like
public class MaintainServicePackageViewModel
{
public IEnumerable<ServicePackageWithOwnerName> ServicePackageWithOwnerName { get; set; }
public ServicePackageWithOwnerName CurrentServicePackage { get; set; }
public IEnumerable<ServiceWithPackageName> ServiceInPackage { get; set; }
}
public class ServicePackageWithOwnerName : ServicePackage
{
[UIHint("DropDownList")]
[DropDownList(DropDownListTargetProperty = "Owner")]
[DisplayNameLocalized(typeof(Resources.Globalization), "OwnerName")]
public IEnumerable<SelectListItem> OwnerName { get; set; }
}
The OwnerName is set to a dropdownlist, but it is not a direct element of the viewmodel instead it's a child element of ServicePackageWithOwnerName which is the element of the viewmodel. In such condition, there's no way to set the OwnerName value in the controller, how to fix this? Appreciate!
Regards
Jack
This is my approach from this post in Code Project:
One Editor Template for all DropDownLists in ASP.Net MVC
I have an int array as a property of a Web User Control. I'd like to set that property inline if possible using the following syntax:
<uc1:mycontrol runat="server" myintarray="1,2,3" />
This will fail at runtime because it will be expecting an actual int array, but a string is being passed instead. I can make myintarray a string and parse it in the setter, but I was wondering if there was a more elegant solution.
Implement a type converter, here is one, warning : quick&dirty, not for production use, etc :
public class IntArrayConverter : System.ComponentModel.TypeConverter
{
public override bool CanConvertFrom(System.ComponentModel.ITypeDescriptorContext context, Type sourceType)
{
return sourceType == typeof(string);
}
public override object ConvertFrom(System.ComponentModel.ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value)
{
string val = value as string;
string[] vals = val.Split(',');
System.Collections.Generic.List<int> ints = new System.Collections.Generic.List<int>();
foreach (string s in vals)
ints.Add(Convert.ToInt32(s));
return ints.ToArray();
}
}
and tag the property of your control :
private int[] ints;
[TypeConverter(typeof(IntsConverter))]
public int[] Ints
{
get { return this.ints; }
set { this.ints = value; }
}
#mathieu, thanks so much for your code. I modified it somewhat in order to compile:
public class IntArrayConverter : System.ComponentModel.TypeConverter
{
public override bool CanConvertFrom(System.ComponentModel.ITypeDescriptorContext context, Type sourceType)
{
return sourceType == typeof(string);
}
public override object ConvertFrom(System.ComponentModel.ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value)
{
string val = value as string;
string[] vals = val.Split(',');
System.Collections.Generic.List<int> ints = new System.Collections.Generic.List<int>();
foreach (string s in vals)
ints.Add(Convert.ToInt32(s));
return ints.ToArray();
}
}
Seems to me that the logical—and more extensible—approach is to take a page from the asp: list controls:
<uc1:mycontrol runat="server">
<uc1:myintparam>1</uc1:myintparam>
<uc1:myintparam>2</uc1:myintparam>
<uc1:myintparam>3</uc1:myintparam>
</uc1:mycontrol>
Great snippet #mathieu. I needed to use this for converting longs, but rather than making a LongArrayConverter, I wrote up a version that uses Generics.
public class ArrayConverter<T> : TypeConverter
{
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
return sourceType == typeof(string);
}
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
string val = value as string;
if (string.IsNullOrEmpty(val))
return new T[0];
string[] vals = val.Split(',');
List<T> items = new List<T>();
Type type = typeof(T);
foreach (string s in vals)
{
T item = (T)Convert.ChangeType(s, type);
items.Add(item);
}
return items.ToArray();
}
}
This version should work with any type that is convertible from string.
[TypeConverter(typeof(ArrayConverter<int>))]
public int[] Ints { get; set; }
[TypeConverter(typeof(ArrayConverter<long>))]
public long[] Longs { get; set; }
[TypeConverter(typeof(ArrayConverter<DateTime))]
public DateTime[] DateTimes { get; set; }
Have you tried looking into Type Converters? This page looks worth a look: http://www.codeguru.com/columns/VB/article.php/c6529/
Also, Spring.Net seems to have a StringArrayConverter (http://www.springframework.net/doc-latest/reference/html/objects-misc.html - section 6.4) which, if you can feed it to ASP.net by decorating the property with a TypeConverter attribute, might work..
You could also do something like this:
namespace InternalArray
{
/// <summary>
/// Item for setting value specifically
/// </summary>
public class ArrayItem
{
public int Value { get; set; }
}
public class CustomUserControl : UserControl
{
private List<int> Ints {get {return this.ItemsToList();}
/// <summary>
/// set our values explicitly
/// </summary>
[PersistenceMode(PersistenceMode.InnerProperty), TemplateContainer(typeof(List<ArrayItem>))]
public List<ArrayItem> Values { get; set; }
/// <summary>
/// Converts our ArrayItem into a List<int>
/// </summary>
/// <returns></returns>
private List<int> ItemsToList()
{
return (from q in this.Values
select q.Value).ToList<int>();
}
}
}
which will result in:
<xx:CustomUserControl runat="server">
<Values>
<xx:ArrayItem Value="1" />
</Values>
</xx:CustomUserControl>
To add child elements that make your list you need to have your control setup a certain way:
[ParseChildren(true, "Actions")]
[PersistChildren(false)]
[ToolboxData("<{0}:PageActionManager runat=\"server\" ></PageActionManager>")]
[NonVisualControl]
public class PageActionManager : Control
{
The Actions above is the name of the cproperty the child elements will be in. I use an ArrayList, as I have not testing anything else with it.:
private ArrayList _actions = new ArrayList();
public ArrayList Actions
{
get
{
return _actions;
}
}
when your contorl is initialized it will have the values of the child elements. Those you can make a mini class that just holds ints.
Do do what Bill was talking about with the list you just need to create a List property on your user control. Then you can implement it as Bill described.
You could add to the page events inside the aspx something like this:
<script runat="server">
protected void Page_Load(object sender, EventArgs e)
{
YourUserControlID.myintarray = new Int32[] { 1, 2, 3 };
}
</script>
You can implement a type converter class that converts between int array and string data types.
Then decorate your int array property with the TypeConverterAttribute, specifying the class that you implemented. Visual Studio will then use your type converter for type conversions on your property.
If you use DataBinding on one of the parent Controls, you can use a DataBinding Expression:
<uc1:mycontrol runat="server" myintarray="<%# new [] {1, 2, 3} %>" />
With a custom expression builder, you can do something similar. The expression builder:
[ExpressionPrefix("Code")]
public class CodeExpressionBuilder : ExpressionBuilder
{
public override CodeExpression GetCodeExpression(System.Web.UI.BoundPropertyEntry entry, object parsedData, ExpressionBuilderContext context)
{
return new CodeSnippetExpression(entry.Expression.Trim());
}
}
Usage:
<uc1:mycontrol runat="server" myintarray="<%$ Code: new [] {1, 2, 3} %>" />