Click here to Skip to main content
15,881,812 members
Articles / Web Development / ASP.NET

Smart web controls in ASP.NET MVC (without HtmlHelper), 7 smart WC implemented in the Catharsis framework

Rate me:
Please Sign up or sign in to vote.
4.33/5 (2 votes)
21 Apr 2009CPOL14 min read 43.5K   21   2
Web-app framework Catharsis and its Web layer implementation

Introduction - Web Layer 

This article concerns on the Web layer of the MVC design pattern. Model-View-Controller is also one of the parts of the web-application framework Catharsis. There is powerful Guidance which will help you to quickly create new solution and extend it with infrastructure for your business entities. 

Guidance.msi, Source code and powerful example can be found: 

http://www.codeplex.com/Catharsis/

Catharsis-title.png


No.Chapter No.ChapterNo.Chapter

I New solution VI CatharsisArchitectureXI Controller layer

II Home page observationVII Entity layer XII Web layer 

III Roles and usersVIII Data layer XIII Tracking, PAX design pattern

IV Localization, translationsIX Business layerXIV Catharsis Dependency Injection (DI, IoC)

V Enter into Catharsis, new EntityX Model layer XV (Code Lists - undone)

Current version is:
Catharsis 1.0.1 (19.4.2009), based on ASP.NET MVC 1.0 and NHibernate 2.1 Alpha2 

MVC should be still OOP  

One of the OOP pillars is “encapsulation”. This requirement is so fundamental, that we should always keep it in mind. When "Encapsulation" is missing, the result is always the spaghetti code, which could be called: structured programming, based on "Functions".
There are in fact three possible approaches to UI (view) in the MVC world. But not all of them keep the above mentioned "Encapsulation" in heart.

Essential MVC WebControl 

This is a pure solution. It is most powerful, clearly OOP, but also most complicated to implement. The principal starts in declaration an Interface IModel. This interface instance is than filled on Controller and finally – the implementing WebControl takes it as a generic parameter <IModel>. View therefore exactly knows the contract and can use all available properties (declared in IModel).

At first look it is not so obvious, that to implement this approach requires lot of effort. It is simply complicated.

Extension methods – HtmlHelper

ASP.NET MVC brings new feature HtmlHelper. This object is as a property available on every WebControl (derived from ViewControl).
You than can call its “extension” methods to create intended html “string”. Yes, exactly – the result is a “STRING” directly rendered to the view.

The only understandable reason for this approach (to render strings) comes form the fact that MVC implemented ASP.NET does not provide binding. You cannot append on UI standard ASP.NET WebControls which expects DataSources or dynamically filled properties. Simply there is no binding event.
ASP.NET MVC therefore provides the dynamically built html streams – “strings” rendered into the View. The “structured programming” (function based) as from the learning book.

Smart WebControls

This is surprisingly nice approach, which is extremely easy to implement, use and re-use. And what’s more, it is based on webControls, which are from the ASP.NET beginnings purely OOP designed, providing the so needed “Encapsulation”. Call the object; use its interface == public properties; let it correctly render output when needed.

Yes, they are OBJECTS. You are really setting theirs properties (CssClassName, InputName, Text etc.) and that’s all. They are not functions or strings. And they are so smart that when they are supplied with all needed inputs (properties) they render themselves as the html stream.

And that all is working in the ASP.NET MVC world - which is without binding event. 

Good - MVC essential web control 

Let’s firstly concern more closely on the first approach – the real MVC WebControls.

pure MVC WebControl 

First of all MVC means Model-Veiw-Controller design pattern. Not the abbr. for ASP.NET MVC!

Now we need an agreement. This would be done by declaring an interface
1) MVC part - Model

interface IControlModel: ICoreModel
{
  Url UrlPath { get; set; }
  string Text { get; set; }
  bool IsDisabled { get; set; }
}  
2) MVC part - Controller
At this moment we know interface and we (controller) are responsible to fill it.
C#
public ActionResult GetControl()
{
  var model = Factory.CreateModel< IControlModel>()
  model.UrlPath = "/Controller/Action"; // simplified
  model.Text = "Link to Action";        // simplified
//model.IsDisabled = false;             // do not set defaults to defaults
  View(model)                           // simplified 
}

3) MVC part - View
The WebControl (View) is designed as:

C#
public class MyControle: ViewControl<IModel> {}

On The .ascx we have to use that all

ASP.NET
<a href='<%= IsDisabled ? string.Empty : Model.UrlPath %>' ><%= Model.Text %></a> 

Weak points  

Agreement checks

On the above implementation are missing agreement checks. What if the View is provided with model, which UrlPath is null? Or if the Text is string.Empty?

Who should do this job? In fact, this job – assuring that the properties are filled – is essential. If you won’t have that check you will sooner or later end up with troubles. Missing checks will be uncovered only accidently – in the runtime. Bad design! 

We have to extend this example with checks. But where they should be?
1) Some could be in directly in the IControlModel implanting object: 

C#
public string UrlPath
{
  get { return _urlPath; }
  set 
  {
    Check.Require(!string.IsNullOrEmpty(value), ' UrlPath must be not null nor empty ');
    _urlPath = value;
  }
}

This is really good design: Implementer of the “agreement” extends the .NET value type checks. .NET can grant that model.UrlPath = -1M won’t be built. Good but not enough. The null or empty string will be passed, we have to extended this requirement using chekcs

Disadvantage of this approach is the fact, that you cannot count on implementer! You are acting with the IControlModel – which can be (even without your agreement) implemented by some proxy, or TestObject etc. In all these scenarios this checks will fail (won't be executed at alll)
So, Model checks for IControlModel are too rigid to be used

2) Controller checks
Simply: These are out of any logic. We need to check the controller’s job! And it cannot be done on controller.

3) WebControl (View) Checks 
"Surprisingly", the consumer of the agreement is the only good place for checks! I believe that (at least now) it is obvious! But trust me, there is lot of “developers”, who did not understand to this trivial and obvious principal! Let’s have a look.


The best and only place where we should check if the contract was fulfilled is the View. More preciously: The WebControls property:

C#
public virtual string UrlPath
{
  get 
  {
    Check.Require(!string.IsNullOrEmpty(Model.UrlPath)
                , ' UrlPath must be not null nor empty ');
    If(Model.IsDisabled)
    {
       return string.Empty; // href='' is good result
    }
    return Model.UrlPath;
  }
} 

On the .ascx it will looks like that

ASP.NET
<a href='<%= UrlPath %>' ><%= Text %></a> 

Great! We did it. And it was very easy in fact.

Design Failure – no ascx.cs files

Some time ago I’ve seen a horrible concept. Some “smart guys” started to use only the .ascx files without any underlying .ascx.cs.
How can anyone implement .only .ascx file for such a fundamental and fragile OBJECT as the WebControl is? You can NEVER count on the IControlModel properties! Please, keep in mind: we are designing the Multi-tier Architecture! What it means? 

On Web layer – you can count on NOONE except of YOU (WebControl designer). You simply do not know how will use your WebControl.
Yes, at first time, the only consumer is you, single developer! But when the project growth, one can make Web layer, the second can fill IControlModels in the controller's action. But he can forget to fill, what you’ve expected to be filled.

There’s no taking without possibility of misstaking!

Web layer, is the layer – not quasi layer. It requires gentle behavior and handling, because it's fragile.
Be paranoiac. Always evaluate provided values! (Or your application user will do it instead of you - expensive tester)
Never forget on Checks!  

Large View, many IControlModels needed 

Another week point of this approach is the need for different IControlModel instance for every WebControl on your View (page or other control).

The above example was to direct and simple, that there was only one IControlModel passed to the view.
In day-to-day scenarios you’ll end up with this View (page):

1) Model as a basket 

C#
interface IMyModel: IWebModel
{
   IControlModel Anchor1Model { get; set; }
   IControlModel Anchor2Model { get; set; }
   …
   IControlModel AnchorNModel { get; set; }
} 

2) Controller’s action will start to growth

C#
public ActionResult GetPage()
{
  var pageModel = Factory.CreateModel< IMyModel>()
  pageModel.Anchor1Model = Factory.CreateModel< IControlModel>()
  pageModel.Anchor1Model = "/Controller/Action"; // simplified
  …
  pageModel.Anchor2Model = Factory.CreateModel< IControlModel>()
  …
  pageModel.AnchorNModel = Factory.CreateModel< IControlModel>()
  …
  View(pageModel) // simplified 
}


3) Page will distribute the asked IControlModel instances as shown

ASP.NET
<myControl:Anchor Id="Anchor1" runat=""server"" ViewDataKey="Anchor1Model" />
<myControl:Anchor Id="Anchor2" runat=""server"" ViewDataKey="Anchor2Model" /><myControl:Anchor Id="AnchorN" runat=""server"" ViewDataKey="AnchorNModel" />

Well. I believe that even in this example is obvious: To complicated for a such a simple controls like anchor.

When should we use MVC WebControls?

MVC WebControls (the ones with specific IControlModel) are suitable for large repeating, very similar blocks. The best example is the “Table” control.
Every entity needs the list view. The table based control which allows paging, sorting, navigating to detail, to edit, to delete …
Good solution is ListWC.ascx control (observe example) with the large IListModel.

There is so much functionality encapsulated in there that it can cover whole article.
In a nut shell: in practice you have to override only ONE method on entitiy's Controller:
OnListToDisplay() and there to fill Model.ListModel.ItemsToDisplay collection.
That’s all. But the result is awesome.

ListWC.png 

Powerful MVC WebControl, RE-usable, easily filled, easily maintained (only one WebControl for list view in whole application – could it be better?)

Bad - MVC design failure - HtmlHelper 

WebControls based on IControlModel are good for larger, repeatable blocks. For smaller ones (yes, for example the Anchor rendering) we have to find out something much more flexible.

HtmlHelper: good servant or bad master?

ASP.NET MVC brought the object HtmlHelper available on ever ViewControl. Firstly (in previews) it was an object with methods, lately was these methods moved to “Extension methods”.

Extension methods

As any feature in .NET, also the extension method can be used correctly or any other way.
Well, let’s have a closer look to three types of extension methods: UrlHelper.Action(), HtmeHelper.WebControl(), object.ToDisplay().

object.ToDisplay() 

There is one fundamental extension method in Catharsis: ToDisplay(). It allows get any object as a string. Important part in this sentence is: any “OBJECT”. The core part of this method is the object itself. It evaluates what the object is:

  • For IPersitent object is used its implement ToDisplay() operation (abstract in the base class). 
  • For decimal, int, short, DateTime it uses the ToString(IFormatProvider, format) operation with user’s current culture and default (or overloaded ToDisplay(format)) formatting.  result is "1 123,45" or "1,123.45" etc.
  • And so on. 

This is a good example of extension method – the core, the essence is the object itself.

UrlHelper.Action()

This extension method needs some parameters like controllerName, action name etc. But again the most important part is UrlHelper itself. It does the environment dependent encapsulation for returning the correct result – the Url path.

It could be

  • “/Controller/Action” or
  • “/Controller.mvc/Action?parameter1=4” or
  • “/VirtualPath/Controller.ashx”.

Great job does this UrlHelper (despite of the fact that is called Helper, which always rises up the “structured programming approach”). It really encapsulates not so trivial routing.

HtmlHelper.WebControl()

There are many extended methods which allow render tags like <a>, <input>. But this is the real design failure. Let's have a look.

HtmlHelper in fact is nothing. This is the “structured programming” design in its worth example. You in fact, have to provide everything what is needed as a parameter (yes, it encapsulates the call to UrlHelper, but it is too few to apologize its existence).

Where is the problem (maybe you do not see it at this moment!)?
Let’s start with very very simplified example: We need an Anchor to be rendered and let’s say there are two parameters Url UrlPath and string Text. Presume that both are preset to default values which can be used (UrlPath = '/', Text = ' ')

To create Extension methods for this scenario we will end up with:

C#
HtmlHelper.Anchor(Url path, string text)
HtmlHelper.Anchor(Url path)
HtmlHelper.Anchor(string text)
HtmlHelper.Anchor() 

In speech of mathematics it is 2n combination (where the n == number of parameters). The result is 2*2 == 4 methods.
Let’s return to our previous example. Imagine that we need to render the anchor and that there are three parameters in play: UrlPath, Text, IsDisabled. Again, some of these parameters can be not provided – their default value will be used.

With a simple counting 2n == 23 == 8 methods.

If we'll be forced to provide more flexibility: CssClassName, Format => 5 paramters 25 == 32 extension methods.
But
only if they are of a different type!!! When CssClassName, Format and Text are string -> we are in troubles because these methods:

C#
HtmlHelper.Anchor(string CssClassName)
HtmlHelper.Anchor(string Format)
HtmlHelper.Anchor(string Text) 

simply cannot decide what parameter you are sending. Desing failure, as already said.
What caused this design failure with HtmlHelper extension methods?
Any other way then correct usage of .NET feature – extension method. In fact this is the “structured programming”, calling the functions, generating spaghetti code.

Why did it happen? This approach was intended as a replacement of so needed and missing “binding”.  <br />HtmlHelper was offered as the string provider, which can use dynamic (in runtime changing) values. Forget this offer. Let’s get out of the “string output” based “Functions”.

Let's Return to paradise (OOP world)

Good - MVC smart web control

ASP.NET world almost ten years supplies us with WebControl approach. In the Form pages (before ASP.NET MVC) they were used almost in 100%. Some of them, where using MVP design pattern, which allowed them to be self-made-men (in the not so good meaning).
Such a WebControl (based on MVP) was calling its Presenter to store the user data (enclosed in the Form) and than ask the Model-Business for new values. They were stored in the IView declared properties.
Some “complicated values” called DataSourceObjects was bounded on the run. That means that the Bind() event was essential.

MVP is dangerous (no center policeman like controller, but independent WebControls, uf) and also out of our scope and interest.

But WebControls itself: this is the best practice approach providing so needed encapsulation, flexibility due to property settings and one place maintenance (WebControl code only).

The OBJECT is back.

Simple WebControl 

So, how should we design our WebControls in the MVC world (without binding)? Let’s start with an example.
1) There’s a code behind first:

C#
public class AnchorControl: ViewControl {
#region members
string _urlPath = '/'   // for example only
string _text =' '  // for example only
#endregion members

#region properties
public virtual string UrlPath
{
  get { return _urlPath; }
  set 
  { 
     Check.Require(!string.IsNullOrEmpty(value), ' Url path must be not null nor empty ');
     // do not worry to append other checks if you need;
     _urlPath = value;
  }
}
public virtual string Text
{
  get { return _text; }
  set 
  { 
     Check.Require(!string.IsNullOrEmpty(value), ' Text must be not null nor empty ');
     // do not worry to append other checks if you need;
     _text = value;
  }
}
#endregion properties
}

2) Now the WebControl from the .ascx view:

ASP.NET
<a href='<%= UrlPath %>' ><%= Text %></a> 

Nothing new.

3) And finally the usage on the Page (or other WebControl)

ASP.NET
<simple:AnchorControl ID='MyAnchor' runat="'server'" 
                           Text='hello' UrlPath='/controller/action' /> 

That’s all. Yes I know: Nothing new. This is the usual WebControl
And that’s exactly what we wanted.   

But it is static control. You have to know all the properties as their string representation – in design time. The property UrlPath was set as the constant (known string)
”/controller/action”
But this scenario is not typical. We usually do not know the value of the UrlPath string in the design time. This value will be probably changing in runtime, and we have to bind it dynamically.

That’s obvious, but how to do that? The tricks like <%$ or <%# or anything else won’t work!

Dynamic WebControl  

1) Eeeeaaasily! Let’s extend our code behind:

C#
public class AnchorControl: ViewControl {
#region members
// the same as above
string _cssClassName = ' cell ';
#endregion members

#region Set()
public virtual AnchorControl SetUrlPath (string urlPath)
{
    Check.Require(!string.IsNullOrEmpty(urlPath), ' urlPath parameter must be not null nor empty');
     // do not worry to append other checks if you need;
     UrlPath = urlPath;
     return this;
}
public virtual AnchorControl SetText (string text)
{
    Check.Require(!string.IsNullOrEmpty(text), ' text parameter must be not null nor empty');
     // do not worry to append other checks if you need;
     Text = text;
     return this;
}
// next depends on your needs? Could be also the CssClassName set dynamically? than:
public virtual AnchorControl SetCssClassName (string cssClassName)
{
    Check.Require(!string.IsNullOrEmpty(cssClassName), 'cssClassName parameter must be not null nor empty');
     // do not worry to append other checks if you need;
     CssClassName = cssClassName;
     return this;
}
#endregion Set()

#region properties
// the same as above PLUS
public virtual string CssClassName
{
  get { return _cssClassName; }
  set 
  { 
     Check.Require(!string.IsNullOrEmpty(value), ' CssClassName must be not null nor empty');
     // do not worry to append other checks if you need;
     _cssClassName += ' ' + value; // if the class cell must be applied anyway
  }
}

#endregion properties
} 

Ou, cool. Easy. Nothing new.

2) The .ascx view:

ASP.NET
<a href='<%= UrlPath %>' class='<%= CssClassName %>' ><%= Text %></a> 

3) And back to our hosted Page (or other hosting WebControl

C#
<% MyAnchor.SetUrlPath(Model.BackUrlPath).SetText('Back'); %> 
<simple:AnchorControl ID='MyAnchor' runat="'server'" 
                           CssClassName='niceFace' Text='Back'  />

Yes, yes, yes. Now we have dynamically bound-able WebControl which:

  • publishes the exact amount of Properties which could be set and used
  • provides the dynamic setters for some (all) published properties. No 2n overflow.
  • encapsulates the “render to string phase” in the WebControl. The OBJECT is back.

Smart WebControls

But we can still go far, much much far. We can assume something, based on our application design.
For example, that our control is hosted on Page or WebControl which is provided with some base model interface “IModel” which can be due to framework restrictions always derived from e.g. ICoreModel (or even from the IWebModel, or IEntityModel as in Catharsis). 

If there is such a presumption: that every IModel is also ICoreModel we can extend our AnchorControl implementation:

C#
public class AnchorControl: ViewControl<ICoreModel> {…}

Now we know, that hosting Page our WebControl is passing to our AnchorControl instance of the ICoreModel and we can access it's properties agreed in the contract ICoreModel!

Dummy example

Dummy EXAMPE now! For simplicity only!!! (Sorry but rather again: very simple assumption for example purposes only - will now follow!
Let’s say that ICoreModel declares property called:

C#
string DefaultCssClassName { get; set;} 

This allows us to extend our AnchorControl:

C#
public class AnchorControl: ViewControl<ICoreModel> {
…
#region properties
// the same as above 
public virtual string CssClassName
{
  get 
  { 
      If (!string.IsNullOrEmpty(Model.DefaultCssClassName)
      {
            return Model.DefaultCssClassName  + ' ' + _cssClassName; // adjust for your needs
      }
      return _cssClassName; 
  }
  set 
  { 
       // the same as above
  }
}
#endregion properties
…
}

More realistic example

Catharsis for example uses common base interface for every entity model called EntityModel. It means that when you are on any entity detail page, you will be provided with model instance which is also IEntityModel.

1) Because many smart WebControls are mostly used on Entity detail pages (but not purely) we can extend our AnchorControl in this way 

C#
public class AnchorControl: ViewControl<ICoreModel> {
#region members
// the same as above
string _urlPath; // default value is null now
#endregion members

#region Set() - public
// the same as above
#endregion Set()

#region Get() - protected
// the same as above
protected virtual string GetHref()
{
    var hrefFormat = ' href=\'{0}\' ';
    If(!string.IsNullOrEmpty(UrlPath))
   {
       return href.FormatWith(UrlPath);
   }
   If (EntityModel.Is()
  {
      return href.FormatWith(UrlHelper(EntityModel.CurrentAction, EntityModel. CurrentController));
   }
   return string.Empty;
}
#endregion Get()

#region properties
// the same as above
public virtual IEntityModel EntityModel 
{
   get   {   return Model as IEntityModel; }// which returns null or instance
}
#endregion properties
}

2) And the .ascx will look like

ASP.NET
<a <%= GetHref() %> class='<%= CssClassName %>' ><%= Text %></a> 

3) And on Page is AnchorControl usage still as easy:

ASP.NET
<% MyAnchor.SetText(Model.Item.ToDisplay()); %>
<simple:AnchorControl ID='MyAnchor' runat="'server'" /> 

 And that's all. Nice and easy. powerful OOP appraoch with strong reuse. And the "Encapsulation" is the motto.

Catharsis Smart WebControls

In the Catharsis you can use some alredy created smart WebControls. Their purpose should be clear, names are self-describing:

Implemented smart WebControls

ASP.NET
<smart:AnchorOrComboBoxWC /> 
<smart:AnchorOrFileWC />
<smart:AnchorOrSearchForWC />
<smart:CheckBoxWC />
<smart:RadioForNullableBoolWC />
<smart:TextOrInputWC />
<smart:TextOrTextAreaWC />

These are based on the approach – do implement everything only ones. So, in Catharsis there is only one View (Page) same for the Detail, New and Edit. Dependently on situation (and smart WebControls settings e.g. ShowReadOnly) the result is rendered:

This for New

 

new.png

 

This one for Detail

detail.png

 

And also Edit

 

edit.png

Smart Web Controls Example

There is a very powerful example, which serves as the showroom for the Catharsis framework.

http://www.codeplex.com/Catharsis/  

Example has large searching options implemented on Data layer, Business rules checks on Façade, Controller preciously fills the ListWC.ascx web control IListModel and the Web (UI) uses all of the implemented smart WebControls.
You must observe it.

Suggestion 

The example took me 4 hours. I used the power of Guidance to create new solution and then Entity infrastructure generator to insert needed entities.

In fact the singificant time-consumer was the implementation (Dao, Façade, UI) – and that’s what we as developers need. To concern on business implementation and to count on the framework as the guy behind. 

Other Catharsis stories: Catharsis.aspx
Enjoy Catharsis
Radim Köhler 

History    

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Web Developer
Czech Republic Czech Republic
Web developer since 2002, .NET since 2005

Competition is as important as Cooperation.

___

Comments and Discussions

 
Generalnice article Pin
zdlik20-May-09 9:07
zdlik20-May-09 9:07 
GeneralRe: nice article Pin
Radim Köhler20-May-09 23:25
Radim Köhler20-May-09 23:25 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.