Hello, harabrachitel.
I want to share the implementation of the
bike , which came in the process of exploring Asp.Net Mvc and developing
lolstore.info . It is convenient for me, it is possible that this will be for you.
First, we formulate the problem / goal:
Make it all hurt. Find a concise and transparent way to transfer several (!) Typed ViewModels from the controller to the View and render them with type checking at the compilation stage.
The situation becomes especially acute when you need one ViewModel (html title, accessible menu, etc.) for the masterpage, another page (for example, a list of anecdotes), and a third page for list (s). Plus it itches to keep easy portability of the same pagelets to other pages.
Well, let's start to understand:
- Strong-typed views: a decent option for low-dependency pages. However, as mentioned above, if you use several embedded masterpages and each has its own model, horror may begin with inheritance, nesting, the class and dependency diagram becomes similar to the habr logo. IMHO such confusion - fe-e ... You can try to separate them by interfaces. Of course, it is possible that they have come up with adequate architectural solutions, which is a pity, but I have not met or are not sharing them;
- ViewData [string key]: universally available from any View involved in the renderer, but a search for a string-key plus the lack of typing makes this method rather sad and potentially buggy. On bezrybe however, it is also possible;
- Own variant: Since the found methods did not suit, I went this way. And I do not regret.
Flight of thought:
What is each ViewModel each separately? - I thought, sipping tea. The answer has matured by itself:
just a container for typed data is, if possible, without logic (I also like rendering logic in the HtmlHelper extension methods).
')
How to transfer some model to existing tools from controller to View? - a sip of drink, -
mm, so there is ViewData, although at first it refused.
How to make type checking at compile time and get rid of string key search? - Generic to the rescue! - bit a cookie.
How to add the implementation, but that it was not necessary to be inherited from something for use? - Taki extensions.
...
Profit:
In fact, it all boiled down to two methods:
- Key generation for ViewData:
public static String GetKey < TModel > ( )
{
return typeof ( TModel ) . FullName ;
}
- Search model or create it with default values:
public static TModel Model < TModel > ( this ViewDataDictionary viewData )
{
String key = GetKey < TModel > ( ) ;
if ( viewData. ContainsKey ( key ) )
{
Object model = viewData [ key ] ;
if ( ( model ! = null ) && ( model. GetType ( ) . Equals ( typeof ( TModel ) ) ) )
{
return ( TModel ) model ;
}
}
TModel newModel = Activator. CreateInstance < TModel > ( ) ;
viewData [ key ] = newModel ;
return newModel ;
}
Other extensions are added for convenience only (there are only a couple of them):
public static TModel Model < TModel > ( this Controller controller )
{
return controller. ViewData . Model < TModel > ( ) ;
}
public static TModel Model < TModel > ( this HtmlHelper htmlHelper )
{
return htmlHelper. ViewData . Model < TModel > ( ) ;
}
As a plus, you can highlight:
- To draw several independent models, you do not need to combine them in any container. Probably the most useful;
- Each small model knows only about itself, as well as the page that renders it. It is convenient to transfer, leaving the controller with the data (as it should be). The weakness of the specific action with the page. As a result, any model can be drawn on any page;
- The model object is created at the first access (from the controller or from the view), there is no need to check for null, to make sure that it is always in ViewData. If you forget to fill it somewhere, it will be with default values;
- There is no need to do StrongTyped views (well, almost disappears;).
Few cons:
- The model must have a public constructor so that Activator can create it. (Doubtful such a minus. In reality, Activator caching constructors were made using Expressions, which is 4 times faster than the original);
- The syntax is a bit complicated.
Using:
In the controller:
public ActionResult Default ( )
{
...
// drawn in pagelet
this . Model < TagListViewModel > ( ) . MyBlaBlaBlaProperty = " lolstore.info " ;
// used on the current for action-master page
this . Model < UserMenuViewModel > ( ) . MyBlaBlaBlaProperty2 = "Jokes" ;
}
On View without strong-type inheritance:
<% = this . Model < TagListViewModel > ( ) . MyBlaBlaBlaProperty %>
In HtmlHelper:
public static MvcHtmlString RenderSomething ( this HtmlHelper htmlHelper )
{
if ( ! htmlHelper. ModelExists < TagListViewModel > ( ) )
{
return new MvcHtmlString ( String. Empty ) ;
}
TagListViewModel model = htmlHelper. Model < TagListViewModel > ( ) ;
// render what the thread
}
The whole entire single code file can be downloaded
here .
ZY If such experiments are interesting to habravchanam and can be applied not only in my projects, then I will lay out the implementation of concurrent and asynchronous caching, a simple AutoMapper (used to clone one type of objects into another without inheritance, interfaces, but tested at the compilation stage) and other utility .
By
EugeneOstapchuk