MVC - Controller layer
This article is next step in Catharsis
documented tutorial. Catharsis is web-application framework gathering
best-practices, using ASP.NET MVC 1.0, NHibernate 2.1 . All
needed source code you can find on
http://www.codeplex.com/Catharsis/

Controller layer
This story is based on the version 1.0.1 and in comparison with
previous (some almost half a year old) it would be correct even in the
future versions.
This article is intended as an tutorial overview for Catharsis developers. The aim is to allow you quick start. Examples how-to develop particular projects will follow...
Controller layer strongly uses the ASP.NET MVC framework and provides two main base classes WebController
and its child EntityController
. Both objects are nested in the ProjectBase.Mvc
project.
WebController
If you will use the Catharsis Guidance, you can be sure that your controllers are always derived from WebController
(entities use the child EntityController
described below)
If you'd like to extend the base functionality, I suggest to implement this into your Company.Product.Controllers
project base classes called BaseWebController
and BaseEntityController
The WebController
encapsulates two fundamental features of the Catharsis framework. (Messages and Model binding)
IList<ApplicationMessage> Messages
WebController
implements the ICoreController
interface. It declares one property Model of type ICoreModel
, which has the collection IList<ApplicationMessage>
. Any descendent controllers therefore can expect that the property Model
contains Model.Messages
. Messages are so important that they will be described in more detail in the future - for now: You can fill it with any Information
, Warning
and/or Error
(including Exceptions). Every Message gathered in this collection during one request is finally displayed to user and logged (if allowed in log4net.config
). If there is any Error
found, you can be sure that no change will be persisted - transaction roll-back is called.
Model binding
The second and I guess very frequently used feature is the "Model binding" functionality. On any controller you can really easily fill your IModel
object with sent data. There is one simple method call available, which you can use for converting the Form
or/and QueryString
data into properties of provided model.
public virtual bool BindModel(object model);
BindModel(MySpecialModel);
As you can see, BindModel()
operation will fill any object (not only IModel
)
Every ‘Key
’ in the pair collections Form
and QueryString
is evaluated: does the name fit to any property of the model
instance?
There is one restriction: property type
must be of the “System
” namespace (decimal
, int
, string
, DateTime
etc.). Any other object must be bounded manually in your derived controllers (see below)
File Binding
Method BindModel()
will also fill the byte[]
properties if there was an byte array uploaded:
<form action='Entity.mvc/Action' method='post'
id='mainForm' enctype='multipart/form-data'>
<input type='file' name='MyFile'>
</form>
will fill the property:
public virtual byte[] MyFile {get; set; }
On UI WebControls always use the CreateForm()
method to generate the FORM
starting tag (end tag is generated automatically after ButtonSubmit
). This method not only uses data from the IMasterModel
(to correctly set action), it also appends the essential attribute enctype='multipart/form-data'
which allows the files uploading.
Binding + Messages
There could be incorrectness in the sent data, misspelling or misunderstanding. The offence against the business law should be checked on the IEntityFacades
. The type
misspellings on the other hand must be handled on the controller
layer – during the Model binding.
The BindModel()
will do that job using the Model.Messages
. For example when binder does not succeed to convert string into a DateTime
property: new Error Message
is inserted into Model.
Messages
collection.
The binding process (iteration through all the 'keys
' in the Form
and QueryString
) is not interrupted, when the first Error
appears! Other words: every 'key
' is evaluated and if needed – a Message
about troubles is appended.
This approach has an impact on user’s comfort. What could be bounded – is bounded. It means that when the user is returned back to the editing page – all correctly filled properties are stored and displayed (no need to retype them) only missing or misspelled are asked to be corrected. User gets the information about all found misspellings at once.
The Catharsis framework also evaluates the Model.Messages
collection on many places. If there is at least one Error
, transactions are rolled-back, and user’s are redirected to editing screens. And also provided with localized information about troubles (another strong ability of the Messages feature).
WebController summary
Keep in mind:
- Insert any
Message
in the controller.Model.Messages
collection and user will be informed about it. - If Logging is allowed, every Message is logged (see
Log4Net.config
) - Insert
Message
with severity ‘Error
’ and current transaction will be rolled-back.
Every Controller
in the application should be (must be) derived from WebController
. The reason is strong reuse of once implemented stuff
EntityController
Catharsis OOP architecture provides powerful entity handling.
The EntityFacades
and EntityDaos
mostly provide only rough CRUD
(create, read, update, delete) operations. In comparison the EntityController
provides more detailed CRUD
handling – to be kind and user friendly. Take a look at this picture:
These regions should give simple, clear idea about implemented features, nested in this base controller.
Every entity can be searched, listed, added, updated and deleted. Users can use paging on the list or detail level, export selected entities into the MS Excel xml 2003
. Every action fills the known properties exposed by IMasterModel
e.g. fills actions, buttons, lists, bread-crumb control path…
CRUD Actions
Add()
, Delete()
… mostly won’t be in the center of your interest. You’ll simply use them without any need to overriding or hiding them (but you can).
More interesting is the design of these actions. Some of them need to be slightly adjusted to fit specific needs of the target entity.
For that purposes there are two types of special operations: OnBefore
and OnAfter
operations
OnBefore operations
Some actions calls the OnBefore
operations (see the list).
#region protected OnBefore Actions
protected virtual void OnBeforeSearch()
protected virtual void OnBeforeSearchingResults(int? id)
protected virtual void OnBeforeList()
protected virtual void OnBeforeListToExcel()
protected virtual void OnBeforeListResults(int? id)
protected virtual void OnBeforeDetail(int id)
protected virtual void OnBeforeEdit(int id)
protected virtual void OnAnyActionWhenBeforeIsFinished() { }
#endregion protected OnBefore Actions
This (OnBefore
operation) is usually the best place for you to override and extend or hide some new features.
Let us take a closer look on the on BeforeSearchAction
:
protected virtual void OnBeforeSearch()
{
Model.ItemModel.DoNotStoreItemInSessionStorage = true;
Model.SearchParam.CurrentPage = 0;
Model.MasterModel.FormUrl = Url.Action(Constants.Actions.EntityDefault, ControllerName);
Model.MasterModel.CurrentAction = Constants.Actions.Common.Search;
Model.ContentCssClass = " searchContent ";
this.AddAction(Constants.Actions.Common.New, Url)
.AddAction(Constants.Actions.Common.Search, Url)
.AddAction(Constants.Actions.Common.List, Url);
Model.MasterModel.ButtonSubmit = new ButtonItem(Constants.Actions.Common.Search);
this.AddButton(Constants.Actions.Cancel.Clear, Url);
OnAnyActionWhenBeforeIsFinished();
}
As you can see the main goal of this operation is the Model filling. The DoNotStoreItemInSessionStorage
switches off the Model.Item to be kept in the ISessionStorage (which can be handy when editing compounded entity – edited item is stored in the session while searching for other entities, CodeLists etc. – before fulfills all business rules and can be stored in the database).
CurrentPage
number is reset to start at the beginning. This is a good (may be necessary) habit, to reset the paging – current page could be higher than the page count after applying new filter.
FormUrl
provides crucial information for the form gathering searched information.
CurrentAction
can be used for example in the bread-crumb control to inform user.
ContentCssClass
allows decorating the main area with different style – it is nice to know that gray background is detail, and the orange is search screen – it is significantly less confusing then common white background. Actions which are available on the searching screen are appended.
ButtonSubmit
- the submit button object (There is only one submit button for the main area <FORM> – other buttons has its own <FORM> element. This new feature of the ASP.NET MVC is in fact very powerful. The form which is submitted sends only its data <input> and that is very effective.)
Finally the OnAnyActionWhenBeforeIsFinished()
is called. It could be handy in situations that you want to extend some Model property for every action.
Other OnBefore
actions work very similarly. (You should take a look)
OnAfter operations
There are four OnAfter operations
#region protected onAfter Actions
protected virtual bool OnAfterBindSearch()
protected virtual bool OnAfterBindModel()
protected virtual void OnAfterStoreModel(ActionExecutedContext filterContext, IDictionary<string, object> otherElements)
protected virtual void OnAfterRestoreModel(ActionExecutingContext filterContext, IDictionary<string, object> otherElements)
#endregion protected onAfter Actions
The OnAfterBindModel
and OnAfterBindSearch
will be probably in permanent use. These operations are the best place to append manual Model binding for properties which are not of the type from the “System
” namespace.
Take a look on a search screen for Users.

User can select one role from the combo box to filter the results. To append this filter to the search criteria we need to implement the AppUser
’s controller OnAfterBindSearch
operation this way:
protected override bool OnAfterBindSearch()
{
var result base.OnAfterBindSearch();
int id;
if (Request.Form.AllKeys.Contains(Str.Controllers.AppRole))
{
if (int.TryParse(Request.Form[Str.Controllers.AppRole], out id))
{
Model.SearchParam.AppRole = FacadeFactory.CreateFacade<IAppRoleFacade>(Model.Messages).GetById(id);
}
else
{
Model.SearchParam.AppRole = null;
}
}
return result;
}
Do not forget to call the base.OnAfterBindSearch()
- the mother class automatically (always) calls to bind the Model.SearchParam - any string, int, DateTime etc. property is therefore bounded this way for you.
OnAfterBindModel
accordingly will be the place for binding your Model.Item
when New()
or Edit()
action is processed.
OnAfterStoreModel and OnAfterRestoreModel
The Catharsis framework uses the approach: New objects per request. It simply means that every user’s action is handled by newly created controller and newly created model. The impact is that nothing can be stored in the models between two requests.
For objects which should be kept among many requests there is the ISessionStorage
(currently uses the ASP.NET Session
, but it could be Cache
cooperating with DB
, or anything else you need).
Any object stored in the ISessionStorage must be space-efficient (simply of a small size). Therefore Catharsis stores only Model.SearchParam
containing the filter criteria and Model.Item
if the entity is edited and cannot be stored yet. Everything else is recreated (usually from DB
or some Cache
or static
elements) for every request.
If you need to store something between some actions – simply override the operations OnAfterStoreModel
and/or OnAfterRestoreModel
. The provided
object IDictionary<string, object> otherElements
is the right place to store your object. But DO NOT forget to remove this object when is not needed!
Note: Keep in mind that user can do unexpected steps. If you expect Action1, Action2, Action3 as a chain, you can store something in ISessionStorage during Action1 and wish to remove it in Action3. But user can change this process and navigate to different action or controller. Therefore you should have some smart ISessionStorage cleaner...
Ajax action GetComboWC
ASP.NET MVC itself strongly, and natively supports AJAX Controls. The reason is in the framework (ASP.NET MVC) architecture, where controller actions can result in Views
or Controls
. The only difference is in the generated html (Page with HTML
BODY
... and Control with only needed <div>
content</div>
)
Catharsis uses async mostly for comboboxes (but you do not have to!). Why render the Page with <Select>
if user may not need it. Better is to provide small button, which after click downloads the dropdown. Until button is clicked Catharsis renders the selected value as hyperlink (proven as user-friendly).
How to create the AJAX combo?
Almost any EntityController
could provide its entities via the ComboBox
. The only limitation is the amount of contained values - 20 entities in the combo is OK but 200 is horrible design.
We’ve already met this on the User search screen – there was the list of the available application roles. How it works?
AppRole controller has the Action GetComboBoxWC()
public virtual ActionResult GetComboBoxWC()
{
var model = ModelFactory.CreateModel<IComboBoxModel>();
model.ComboBoxName = ControllerName;
string currentID = null;
model.ShowEmpty = true;
if (Request.Params.AllKeys.Contains(Str.WebControls.Ajax.ParamCurrentID))
{
currentID = Request.Params[Str.WebControls.Ajax.ParamCurrentID];
}
if (Request.Params.AllKeys.Contains(Str.WebControls.Ajax.ParamShowEmpty)
&& Request.Params[Str.WebControls.Ajax.ParamShowEmpty].ToLower() == "false")
{
model.ShowEmpty = false;
}
model.Items = new SelectList(Facade.GetAllRoles()
, Str.Common.ID
, Str.Business.AppRole.RoleName
, currentID);
if (Request.Params.AllKeys.Contains(Str.WebControls.Ajax.ParamCssClassName))
{
model.CssClass = Request.Params[Str.WebControls.Ajax.ParamCssClassName];
}
return View(Str.WebControls.ComboBoxWC, model);
}
The essence of every Action
is the IModel
filling. As you can see, all the processing has one aim: gather data (Form
, QueryString
, IFacade
) and fill the model which is passed to smart View - to render ComboBoxWC
. Separation of concern in its base form.
You can use the copy-paste to extend your other controller with this Action. The changes will go mainly the new SelectList()
statement - dependent on your entity IFacade.
Next step is to put small piece of code into the EntityDetailWC.ascx
:
<div class="inputWC inputWC70 w100p">
<div class="label"><%= GetLocalized(Str.Controllers.AppRole)%></div>
<div class="input" <%= TagId("div" + Str.Controllers.AppRole) %>> <%= this.TextOrComboBox(Str.Controllers.AppRole
, Str.WebControls.Ajax.ActionGetComboBoxWC, Model.SearchParam.AppRole, "div" + Str.Controllers.AppRole)%></div>
</div>
(InputWC70 will grant 70% of space to the second div class="input" )
This will result in the ComboBox
(hyperlink
and button
till clicked)
displayed on a picture above.
The last part is to Bound this information
- again as described above (OnAfterBindSearch()
)
OnList operations
The List()
action would be one of that you will never override (but still: you can). Displaying the list is in fact very straightforward: Just to call IEntityFacade
with Model.SearchParam
and get the IList<Entity>
.
The only and essential difference is in the displayed columns. And that is the job of the OnListToDisplay
operation.
DO NOT call Base.OnListToDisplay()
- OnList actions, as opposite to others, should fully hide the base implementation.
The only purpose of this method is to fill the Model.ListModel.ItemsToDisplay
collection
protected override void OnListToDisplay()
{
Model.ListModel.ItemsToDisplay = Facade.GetBySearch(Model.SearchParam)
.Select(i => new ItemToDisplay()
{
ID = i.ID,
Description = i.ToDisplay(),
Items = new List<IHeaderDescription>
{
new HeaderDescription { HeaderName = Str.Business.AppUser.Login, Value = i.Login} ,
new HeaderDescription { HeaderName = Str.Business.AppUser.SecondName , Value = i.SecondName },
new HeaderDescription { HeaderName = Str.Business.AppUser.FirstName , Value = i.FirstName } ,
new HeaderDescription { HeaderName = Str.Common.ID , Value = i.ID.ToString(), Align = Align.right },
}
} as IItemToDisplay);
}
The basic skeleton of this operation is generated by Guidance. And as the implementation on AppUser
controller shows, your goal is to append as many new HeaderDescriptions as needed.
Than the ListWC.ascx
WebControl comes into play and uses this information to render the localized, orderable list, with navigation, edit and delete buttons, displaying checkboxes for bool values etc.
That all is possible because of this rich object
new HeaderDescription()
{
HeaderName = "The key which will be localized",
HeaderNameArea = " Area for more precious localizing ",
IsBoolean = true,
Align = Align.right,
Localize = true,
MaxCellLength = 20,
Sort = true,
SortByObject = "Boss.Address",
SortByProperty = "City",
Value = "string which will be displayed in the cell "
}
OnListToExcel
is very similar. It provides the columns which should be exported into the
MS Excel xml 2003
file.
Controller summary
MVC design pattern is easy to use and understand, allowing and supporting powerful architecture. I believe that this story will help you to use it - in a full power with Catharsis.
Enjoy Catharsis
Radim Köhler
Sources
All needed source code you can find on
http://www.codeplex.com/Catharsis/
How do I see the MVC ver. MVP:http://www.codeproject.com/KB/aspnet/AspNetMvcMvp.aspx
History