DotNetAge - Mvc & jQuery CMS
Hide sidebar

Component Model for ASP.NET MVC3


Overview


In this article i will show you how to build a component model for ASP.NET MVC3 with Builder and Factory design patterns. You could use this model to write your strongly type components to binding data, handling hierarchical objects and write jQuery UI controls / Html5 controls for ASP.NET MVC3.

Background


As we know in classical ASP.NET we could write controls by using ASP.NET control model for our web applications. Today in MVC3 we’re usually write helpers instead of controls. I think helper methods is a good choice for some of simple use scenarios but not for all. For render single tag, img or scripts helper methods does well. I can help thing how to develop more complex controls with help methods ? How to write template container controls ? How to write a data bindable treeview and menu ? How to write and use that complex controls easily ? In fact i love the helper method and i hate them at the same time. Because helper methods is the easy way to output my tags on view, but that is not OOP way ! Helpers is easy to use but hard to extend and maintenance. How could you read, remember and maintenance a method with over 10 overload version ? That is hell! So we need another way out! The way that could easy to learn, more readable, OOP , extensible and familiar for our control developing.

Before you start


There are some concepts you need to know:


Objectives


We need a component model for ASP.NET MVC
  • Supports Razor and Web Form syntax.
  • Offers interfaces or abstract classes to implement.
  • Supports control template.
  • Supports composite components.
  • Supports data binding.
  • Easy to integrate with client ability (javascripts jQuery).

Usage Design


The biggest difference between Mvc3 and classic ASP.NET is Mvc3 using Views and helpers to render html tags directly but no any component model like classic ASP.NET. So before design the component model we need to know how many ways could write controls in Mvc3 then we also need to know the common behaviors, properties, usages of the controls. Now let’s look back in Mvc3 and do some testing.

Usage1:Extension method


We calls “Html.XXX()” and “Ajax.XXX()” Mvc Helpers or Extensions,actually they are static extension method that is a good way to inject new methods to an exists class. When you are using Razor it provides a new syntax to write the helper method by using @helper. I put build in helpers which we are often use in the following list,(notes:each method has more then 5 overload versions.):

  • Html.Display()
  • Html.DisplayFor()
  • Html.Label()
  • Html.LabelFor()
  • Html.LablelForModel()
  • Html.ActionLink()
  • Html.RoutLink()
  • Html.Editor()
  • Html.EditorFor()
  • Html.EditorForModel()
  • Html.CheckBox()
  • Html.CheckBoxFor()
  • Html.Hidden()
  • Html.Password()
  • Html.PasswordFor()
  • Html.RadioButton()
  • Html.RadioButtonFor()
  • Html.TextBox()
  • Html.TextBoxFor()
  • Html.ListBox()
  • Html.ListBoxFor()
  • Html.DropDownList()
  • Html.DropDownListFor()
  • Html.TextArea()
  • Html.TextAreaFor()




There are almost over 100 methods with overload versions of this list! Please note that these helpers only use to render <input> <a> <select> <textArea> tags maybe call forms. Why so many overloads of helper has ?! Just for “DIY“ and “Clearn” i think. Although the helpers would be more simple to use but as we see they will bings a large numbers of overloads. They are become more unreable, hard to remember and hard to maintenance.



Conclusion : The helper methods should be used for simply controls without many options. That is not OOP way.



Usage 2: Template


In classic ASP.NET we would usually Template in our container control that is awesome! But how could we do that in Mvc ? I couldn’t found any solution in ASP.NET official website but only write helper methods or view files. How to write a strongly type control that enabling template feature ? For example how to write a Panel control in Mvc3 ? Even for form tag the Mvc offers two strange methods ”BeginForm”, “EndForm” to implement it. Why not provides a method like this:



Razor:

@{
Html.Form()
.Body(@<text>
<p>Here is form body</p>
</text>)
.End();
}


Web form:

<%
Html.Form()
.Body(%>
<p>Here is form body</p>
<%)
.End();
}
%>




That is what i want. As you see Web Form and Razor has imputations of Template. In Web Form syntax we should use Action delegate to embed other html tags. For Razor ViewEngine only allows using inline block text @<text></text> ,  in the example above i use “Builder” design pattern act as control role of M-V-C pattern.



OK, i have found the way to implement Template for controls. That is very useful for every container controls.

Usage 3: Composite controls


At the age of the classic ASP.NET (in my time i am a old man you know ^^), i remember i had ofen implement the CompositeControl base class of my complex controls. Obvious ,when our control become more complex ,we need to provide more options for them to control their behaviors, only use helper methods is not enough. We a component model to implement the composite controls such as TreeView,Grid, ComobBox, ListBox, Menu, SiteMap, Toolbar and as so on. These controls are common controls we would use them any where and any time.



So let’s image how to use these “Controls” in our Mvc project:




@{
Ajax.DJME().ComboBox()
.Width(200)
.Items(items=>{
items.Add("text1","value1");
items.Add("text1","value1");
items.Add().Template(@<text><a href="#">I am template item.</a></text>);
})
.Render();
}

@{
Ajax.DJME().TreeView()
.Nodes(nodes=>{
nodes.Add("node1","http://domain.com");
nodes.Add("node2","http://domain.com");
nodes.Add("node3","http://domain.com")
.Nodes(children=>{
children.Add("subNode1","http://sub.domain.com");
});
})
}





Composite usage is very similar to the Template usage.It also use Action delegate to implement it.

Usage 4 : Data binding


In Mvc way we will generate the Model instance in Controller side and pass it in ViewData.Model to View side. So our component only run in View side we should not concern how to generate model, it just a parameter for controls. And data binding just the automatic version of the Composites usage. The data binding usage maybe like this:




@{
Ajax.DJME().ListBox()
.Bind(Model)
.Render();
}

@{
Ajax.DJME().Grid(Model)
.AutoGenerateColumns()
.Sortable()
.Pagable()
.Render();
}


Component Model:Using Builder and Factory method design patterns to implement M-V-C Pattern


We previously discussed a number of usages in the MVC may be written, where we will be more to explore in depth how to build an abstract level object model and implemention.



First, before discussing the object model design, you need to remember that ASP.NET MVC is only a framework or a set of development tools, in face M-V-C is a very classic design patterns. So our component model will be implementing the MVC pattern. Now let’s take a look in the class diagram of the abstraction level objects.



djme_abstraction.png




Model


We should not concern how to generate the model. For components Model just use as a external data source.

View


The ViewComponent base class act as View, it use to hold properties values and render the html content. The sub classes overide the RenderXXX() methods to write the html contents to View response. The ViewComponent is similar to classic ASP.NET Control base class. The follow code is written in c# for the ViewComponent.


public abstract class ViewComponent
{
private ViewContext _viewContext;
private string _id;

public ViewContext ViewContext
{
get { return _viewContext; }
}

public ViewComponent(ViewContext viewContext)
{
this._viewContext = viewContext;
}

public virtual string Name { get; set; }

public virtual string ClientID
{
get
{
if ((string.IsNullOrEmpty(_id)) && (!string.IsNullOrEmpty(Name)))
{
var _tag = new TagBuilder(TagName);
_tag.GenerateId(Name);
if (_tag.Attributes.ContainsKey("id"))
_id = _tag.Attributes["id"];
else
_id = Name;
}
return _id;
}
private set { _id = value; }
}

public virtual string TagName
{
get
{
return "div";
}
}

public virtual void Render(HtmlTextWriter writer)
{
RenderBeginContent(writer);

RenderConent(writer);

RenderEndConent(writer);
}

public virtual void RenderBeginContent(HtmlTextWriter writer)
{
TagBuilder _tag = new TagBuilder(TagName);

if (!string.IsNullOrEmpty(Name))
{
_tag.GenerateId(Name);
if (!_tag.Attributes.ContainsKey("id"))
_tag.MergeAttribute("id", Name);

Id = _tag.Attributes["id"];

if (TagName.Equals("input", StringComparison.OrdinalIgnoreCase) || TagName.Equals("textarea", StringComparison.OrdinalIgnoreCase))
_tag.MergeAttribute("name", Name);
}

writer.Write(_tag.ToString(TagRenderMode.StartTag));
}

public virtual void RenderConent(HtmlTextWriter writer) { }

public virtual void RenderEndTag(HtmlTextWriter writer)
{
writer.WriteEndTag(TagName);
}

public string GetHtml()
{
var result = new StringBuilder();
using (var writer = new HtmlTextWriter(new StringWriter(result)))
{
Render(writer);
}
return result.ToString();
}
}


Control


Then ViewComponentBuilder act as Control role of MVC pattern and it implement the Builder design pattern. It controls the ViewComponent build progress.


public class ViewComponentBuilder<TComponent,TBuilder>
where TComponent : ViewComponent
where TBuilder : ViewComponentBuilder<TComponent, TBuilder>
{
public ViewComponentBuilder(TComponent component)
{
this.Component = component;
}

private TComponent component;

public TComponent Component
{
get { return component; }
private set { component = value; }
}

public ViewContext ViewContext
{
get
{
return component.ViewContext;
}
}

public TBuilder GenerateId()
{
if (string.IsNullOrEmpty(Component.Name))
{
string prefix = Component.GetType().Name;
string key = "DJME_IDSEQ_" + prefix;
int seq = 1;

if (ViewContext.HttpContext.Items.Contains(key))
{
seq = (int)ViewContext.HttpContext.Items[key] + 1;
ViewContext.HttpContext.Items[key] = seq;
}
else
ViewContext.HttpContext.Items.Add(key, seq);
Component.Name = prefix + seq.ToString();
}

return this as TBuilder;
}

public virtual TBuilder Name(string name)
{
Component.Name = name;
return this as TBuilder;
}

public virtual void Render()
{
RenderComponent();
}

protected void RenderComponent()
{
using (var writer = new HtmlTextWriter(ViewContext.Writer))
{
Component.Render(writer);
}
}

public virtual MvcHtmlString GetHtml()
{
return MvcHtmlString.Create(Component.GetHtml());
}
}




In client side the developer will not use the ViewComponent directly but ViewComponentBuilder. In order to use the builder and write the code inline each control method of builder should return it self. We need to call Render() or GetHtml() method to get the build result after finished the build progress.



The following code is the usage of the ViewComponentBuilder

@{
Ajax.YourControl
.Name("Your control name")
.Render();
}




Why the ViewComponentBuilder has Render() and GetHtml() result getting methods ?



Because of considerations of the WebForm and Razor's ViewEngines output mechanisms.The following list is the explanation of these methods:
  • Render - Invoke by ViewEngine and write the ViewComponent output result to response. It will be lazy called and return nothing.
  • GetHtml – It will be return the ViewComponent output result as MvcHtmlString when it was invoked.

Factory method and Helper method


Finally we need a class to construct the ViewComponent and ViewCompoentBuilder and put them work together. And also need to return the builder and inject the construct method into HtmlHelper or AjaxHelper. Now we will using [/publishing/home/2011/06/17/6857/factory-method.html|Factory method design pattern] to implement it .



  1. 1. Create an extension method and inject to HtmlHelper that make Html.Demo() could be return a ComponentFactory instance.




public static class Extenstions
{
public static SimpleFactory Demo(this HtmlHelper helper)
{
return new SimpleFactory (helper.ViewContext);
}
}




  1. 2. Create a component factory that contains the ViewComponentBuilder construct methods.




public class SimpleFactory
{
private ViewContext _viewContext;

public ViewContext ViewContext
{
get { return _viewContext; }
}

public SimpleFactory (ViewContext viewContext)
{
this._viewContext = viewContext;
}

public TextViewComponentBuilder Text()
{
return new TextViewComponentBuilder (new TextViewComponent(this.ViewContext)).GenerateId();
}
}




  1. 3. We could use the factory like this:


@Html.Demo().Text().GetHtml()






The factory does not inherit from any base classes. It only supporting construction of ViewComponentBuilder. You can define various of factories to group different kinds of components methods. The factory method and static extension method together can bring us various of challenge.We can use abstract factory to create a serial of component models having same behavior. You can even use DI in static extension method to create/construct component factory dynamically.Imaging you write a component set of Html Input, and you can porvide a component set of Html5 input or jQuery input later. Just modify abstract factory in static methods.You can inject the implement of abstract factory via DI. How to dynamically construct component is a vivid topic, while i just offer here. I would write some articles to share with you later in this topic.

Tutorial 1:Implement Html5 input controls


Now let’s use this component mode to create a Html5 inputs control set.


The following figure is the class diagram of the Html5 input classes.



Html5Input_Classes.png




Step1:Implement ViewComponent


Create Html5InputViewComponent derived from ViewComopnent.Override the Render method and render the input html tag




public class Html5InputViewComponent:ViewComponent
{
public Html5InputViewComponent(ViewContext viewContext) : base(viewContext) { }

private Html5InputTypes inputType = Html5InputTypes.Text;

public Html5InputTypes InputType
{
get { return inputType; }
set { inputType = value; }
}

public object Value { get; set; }

public override void Render(System.Web.UI.HtmlTextWriter writer)
{
TagBuilder input = new TagBuilder("input");
input.GenerateId(this.Name);

if (InputType.Equals(Html5InputTypes.DatetimeLocal))
input.MergeAttribute("type", "datetime-local");
else
input.MergeAttribute("type", inputType.ToString().ToLower());

if (!input.Attributes.ContainsKey("name"))
input.Attributes.Add("name", this.Name);

if (Value != null)
input.MergeAttribute("value", Value.ToString());

writer.Write(input.ToString(TagRenderMode.SelfClosing));
}
}


Step2:Implement ViewComponentBuilder


Create a Html5InputViewComponentBuilder to control the Html5InputViewComponent.In this case we only add a "Value" method to set the Html5InputViewComponent.Value property.




public class Html5InputViewComponentBuilder : ViewComponentBuilder<Html5InputViewComponent, Html5InputViewComponentBuilder>
{
public Html5InputViewComponentBuilder(Html5InputViewComponent component) : base(component) { }

public Html5InputViewComponentBuilder InputType(Html5InputTypes type)
{
Component.InputType = type;
return this;
}

public Html5InputViewComponentBuilder Value(object value)
{
Component.Value=value;
return this;
}
}


Step3:Create a Factory


The Html5ViewComopnentBuilderFactory class contains a set of construe methods. This factory provide some simplify wrapper method that pre build the Html5InputViewComponent.




public class Html5ViewComponentBuilderFactory
{
private ViewContext _viewContext;

public ViewContext ViewContext
{
get { return _viewContext; }
}

public Html5ViewComponentBuilderFactory(ViewContext viewContext)
{
this._viewContext = viewContext;
}

public Html5InputViewComponentBuilder Input(Html5InputTypes type)
{
return new Html5InputViewComponentBuilder(new Html5InputViewComponent(this.ViewContext))
.GenerateId()
.InputType(type);
}

public MvcHtmlString Number(int value=0)
{
return new Html5InputViewComponentBuilder(new Html5InputViewComponent(this.ViewContext))
.GenerateId()
.InputType(Html5InputTypes.Number)
.Value(value)
.GetHtml();
}

public MvcHtmlString Range(int value = 0)
{
return new Html5InputViewComponentBuilder(new Html5InputViewComponent(this.ViewContext))
.Value(value)
.GenerateId()
.InputType(Html5InputTypes.Range)
.GetHtml();
}

public Html5InputViewComponentBuilder Date()
{
return new Html5InputViewComponentBuilder(new Html5InputViewComponent(this.ViewContext))
.GenerateId()
.InputType(Html5InputTypes.Date);
}
}


Step 4: Write an extension method to inject the Factory into HtmlHelper




public static class Extenstions
{
public static Html5ViewComponentBuilderFactory Html5(this HtmlHelper helper)
{
return new Html5.Html5ViewComponentBuilderFactory(helper.ViewContext);
}
}


Step5: Write the client code in View



<fieldset>
<legend>Html5 Input components demo</legend>
<p>
Number:
</p>
<p>
@Html.Html5().Number(20)
</p>
<p>
Range:
</p>
<p>
@Html.Html5().Range(5)
</p>
<p>
Date:
</p>
<p>
@Html.Html5().Date()
</p>
</fieldset>




html5_run_result.png

Templating


In order to add the template ability to component i defined an IHtmlTemplate interface.

public interface IHtmlTemplate
{
Action Content { get; set; }

Func<object, object> InlineContent { get; set; }

bool IsEmpty { get; }

void WriteTo(HtmlTextWriter writer);
}




The IHtmlTemplate provides two template properties:

  • Content – This template property is an Action delegate that allows developers using it in View like this:{{{ Content(()=>{ } }}}. It can support WebForm and Razor syntax.
  • InlineContent – This template property only work for Razor ViewEngine. Razor recognizes Func<object,object> deletage and render it as inline text block @<text>.



Implement IHtmlTemplate interface:




public class HtmlTemplate : IHtmlTemplate
{
public Action Content { get; set; }

public Func<object, object> InlineContent { get; set; }

public void WriteTo(System.Web.UI.HtmlTextWriter writer)
{
if (Content != null)
{
Content.Invoke();
}
else
{
if (InlineContent != null)
writer.Write(InlineContent(null).ToString());
}
}

public bool IsEmpty
{
get
{
return ((Content == null) && (InlineContent == null));
}
}
}




In the following tutorial i will show you how to use the IHtmlTemplate and HtmlTemplate in components.




Tutorial 2: Container Component


This tutorial i how to create a Panel component with body template.


Step1. Implement PanelViewComponent


Add a PanelViewComponent derive from ViewComponent. Add a Content property and set the type as HtmlTemplate (It also could be set to IHtmlTemplate).




public class PanelViewComponent:ViewComponent
{
public PanelViewComponent(ViewContext context) : base(context)
{
Content = new HtmlTemplate();
}

public HtmlTemplate Content { get; set; }

public string Title { get; set; }

public override void RenderConent(System.Web.UI.HtmlTextWriter writer)
{
writer.WriteFullBeginTag("div");
writer.Write(Title);
writer.WriteEndTag("div");

if (!Content.IsEmpty)
Content.WriteTo(writer);
}
}


Step2.Implement PanelViewComponentBuilder

public class PanelViewComponentBuilder:ViewComponentBuilder<PanelViewComponent,PanelViewComponentBuilder>
{
public PanelViewComponentBuilder(PanelViewComponent component) : base(component) { }

public PanelViewComponentBuilder Title(string title)
{
Component.Title = title;
return this;
}

public PanelViewComponentBuilder Body(Action body)
{
if (body != null)
Component.Content.Content = body;
return this;
}

public PanelViewComponentBuilder Body(Func<object, object> body)
{
if (body != null)
Component.Content.InlineContent = body;
return this;
}
}



We expose the Action and Func delegate in Body methods to developers.


Step3: Add the factory method




public static class Extenstions
{
public static DNA.Mvc.ComponentModel.Html5.Html5ViewComponentBuilderFactory Html5(this HtmlHelper helper)
{
return new Html5.Html5ViewComponentBuilderFactory(helper.ViewContext);
}

public static PanelViewComponentBuilder Panel(this HtmlHelper helper)
{
return new PanelViewComponentBuilder(new PanelViewComponent(helper.ViewContext)).GenerateId();
}
}


Step4:Using the Panel in View

@{
Html.Panel()
.Title("Panel title")
.Body(@<text>
<p>
Hi here is panel body contents.
</p>
</text>)
.Render();
}


Composites component


As is stated above, it becomes a complex component when our component combines different sub-components. Basically, ViewComponet, ViewComponentBuilder and IHtmlTemplate can implement most of components. And what we mostly think of complex components is:


  • How to construct children components programmatically.
  • How to construct children components for data binding.


Simply, composite components are made of component container and children components. In many case the composite component is made up of many different types of children components. In order to make the usage of children components with container in same way, we should pass a constructor method of child component builder in container Builder method context. It reduces the coupling and easy to make changing.


Tutorial 3: LinkList


In this tutorial i will show you how to create a simple list component. It make of a list container (LinkList) and numbers of link list item components. I will show how to add the link list item within the linklist builder method.




Step1: Create container component and children component




public class LinkListComponent:ViewComponent
{
public LinkListComponent(ViewContext context) : base(context) { Links = new List<LinkComopnent>(); }

public virtual ICollection<LinkComopnent> Links { get; set; }

public override string TagName
{
get
{
return "ul";
}
}

public override void RenderConent(System.Web.UI.HtmlTextWriter writer)
{
foreach (var link in Links)
{
link.Render(writer);
}
}
}

public class LinkListComponentBuilder:ViewComponentBuilder<LinkListComponent,LinkListComponentBuilder>
{
public LinkListComponentBuilder(LinkListComponent component) : base(component) { }

public LinkListComponentBuilder Items(Action<LinkComponentBuilderFactory> items)
{
var factory=new LinkComponentBuilderFactory(this.Component);

if (items != null)
items.Invoke(factory);

return this;
}
}

public class LinkComopnent:ViewComponent
{
public LinkComopnent(ViewContext context) : base(context) { }

public string Title { get; set; }

public string Url { get; set; }

public override string TagName
{
get
{
return "li";
}
}

public override void RenderConent(System.Web.UI.HtmlTextWriter writer)
{
var a = new TagBuilder("a");
a.MergeAttribute("href", Url);
a.InnerHtml = Title;
writer.Write(a.ToString());
}
}

public class LinkComponentBuilder:ViewComponentBuilder<LinkComopnent,LinkComponentBuilder>
{
public LinkComponentBuilder(LinkComopnent component) : base(component) { }

public LinkComponentBuilder Title(string title)
{
this.Component.Title = title;
return this;
}

public LinkComponentBuilder Url(string url)
{
Component.Url = url;
return this;
}

}




Step2: Add children component builder factory and container component builder




public class LinkComponentBuilderFactory
{
public LinkListComponent ItemContainer { get; set; }

public LinkComponentBuilderFactory(LinkListComponent container)
{
this.ItemContainer = container;
}

public LinkComponentBuilder Add()
{
var item=new LinkComopnent(ItemContainer.ViewContext);
ItemContainer.Links.Add(item);
return new LinkComponentBuilder(item);
}
}

public class LinkListComponentBuilder:ViewComponentBuilder<LinkListComponent,LinkListComponentBuilder>
{
public LinkListComponentBuilder(LinkListComponent component) : base(component) { }

public LinkListComponentBuilder Items(Action<LinkComponentBuilderFactory> items)
{
var factory=new LinkComponentBuilderFactory(this.Component);

if (items != null)
items.Invoke(factory);

return this;
}
}




Step2: Add children component builder factory and container component builder




public class LinkComponentBuilderFactory
{
public LinkListComponent ItemContainer { get; set; }

public LinkComponentBuilderFactory(LinkListComponent container)
{
this.ItemContainer = container;
}

public LinkComponentBuilder Add()
{
var item=new LinkComopnent(ItemContainer.ViewContext);
ItemContainer.Links.Add(item);
return new LinkComponentBuilder(item);
}
}




  • Create the children component and add to container component.
  • Create children component builder and bind to children component.
  • Return the children component builder to client view.




public class LinkListComponentBuilder:ViewComponentBuilder<LinkListComponent,LinkListComponentBuilder>
{
public LinkListComponentBuilder(LinkListComponent component) : base(component) { }

public LinkListComponentBuilder Items(Action<LinkComponentBuilderFactory> items)
{
var factory=new LinkComponentBuilderFactory(this.Component);

if (items != null)
items.Invoke(factory);

return this;
}
}





We pass the LinkComponentBuilderFactory as a parameter in Action delegate to client view.



Step3:Using in View

@{
Html.LinkList()
.Items(items =>
{
items.Add().Title("Item1").Url("#");
items.Add().Title("Item1").Url("#");
items.Add().Title("Item1").Url("#");
})
.Render();
}




Now we provides the way to add the children component in View by manual. When we want the component could create the child itself that is another usage - DataBinding.


We only do some change that make the LinkListComponentBuilder support binding to data Model.

public class LinkListComponentBuilder : ViewComponentBuilder<LinkListComponent, LinkListComponentBuilder>
{
public LinkListComponentBuilder(LinkListComponent component) : base(component) { }

public LinkListComponentBuilder Items(Action<LinkComponentBuilderFactory> items)
{
var factory = new LinkComponentBuilderFactory(this.Component);

if (items != null)
items.Invoke(factory);

return this;
}

public LinkListComponentBuilder Bind<T, TKey, TValue>(IEnumerable<T> model,
Expression<Func<T, TKey>> keySelector,
Expression<Func<T, TValue>> valueSelector)
where T : class
{
if (model != null)
{
this.Items(items=>{
foreach (var t in model)
{
items.Add()
.Title(keySelector.Compile().Invoke(t) as string)
.Url(valueSelector.Compile().Invoke(t) as string);
}
});
}
return this;
}
}






@{
var listitems = new List<DNA.Demo.Web.Collation>();
listitems.Add(new DNA.Demo.Web.Collation() { Key="BindingItem1",Value="#" });
listitems.Add(new DNA.Demo.Web.Collation() { Key = "BindingItem2", Value = "#" });
listitems.Add(new DNA.Demo.Web.Collation() { Key = "BindingItem3", Value = "#" });

Html.LinkList()
.Bind(listitems, m => m.Key, m => m.Value)
.Render();
}










 


    Average:4
  • Reads
    (9195)
  • Permalink
Share to: Add to del.icio.us Digg! Share on Google Buzz Share on Facebook Reddit! Stumble it! Share on Twitter

Tag cloud

Anything in here will be replaced on browsers that support the canvas element