ASP.NET MVC - Typed ViewData proposal -
in project i'm working on, have lot of "read-only" pages don't have <form>
s in; have lot of form pages have lot of readonly data pulled in controller.
ordinarily you'd use viewmodels , have 1 viewmodel per view , viewmodel contains of data view. seems fair enough, except there's problem:
in head, see viewmodel being representation , encapsulation of entire data sent view controller, viewmodel might contain data populated controller (such selectlistitem[] html.dropdownlistfor() data) cannot populated view , sent controller.
sure, possible have data part of viewmodel , manually re-populate before returning view in controller's httppost-handling method, feel needlessly complicates controller's code (and you'd have use updatemodel() instead of automatic updates happen when specify model argument action method).
my solution typed viewdata object. derive viewpage<tmodel>
give viewpage2<tmodel,tdata> tdata : viewdatadictionary<tmodel>
, override (or shadow) .viewdata
property return instance of tdata instead..
my questions twofold:
- subclassing viewpage seems easy enough, put logic handle initialisation of
viewpage2<tmodel,tdata>
class? - is there wrong approach?
i found way implement viewdata / viewmodel approach without messing asp.net mvc.
here's how did it:
this viewpage2
class exposes strongly-typed viewdata object views. unfortunately need use reflection avoid viewpage's behaviour of creating brand new viewdatadictionary, statically cached fieldinfo objects it's no more expensive mvc's dynamic controller/action lookup in routing.
public class viewpage2<tmodel,tdata> : viewpage<tmodel> tdata : viewdatadictionary<tmodel> { public viewpage2() : base() { } private boolean _datapresent; private tdata _data; public new tdata viewdata { { if( _datapresent && _data == null ) { _data = (tdata)base.viewdata; } return _data; } } // cached in static class state performance. private static readonly fieldinfo _viewpage1viewdata; private static readonly fieldinfo _viewpage2viewdata; static viewpage2() { type viewpage1 = typeof(viewpage<tmodel>); _viewpage1viewdata = viewpage1.getfield("_viewdata", bindingflags.instance | bindingflags.nonpublic ); type viewpage2 = typeof(viewpage); _viewpage2viewdata = viewpage2.getfield("_viewdata", bindingflags.instance | bindingflags.nonpublic ); } protected override void setviewdata(viewdatadictionary viewdata) { // viewpage<tmodel> creates new viewdatadictionary<tmodel> when method called, if viewdata of correct type. // trick reimplement setviewdata , set base._viewdata , basebase._viewdata if( viewdata tdata ) { _viewpage1viewdata.setvalue( this, viewdata ); _viewpage2viewdata.setvalue( this, viewdata ); _datapresent = true; } else { base.setviewdata( viewdata ); } } }
then each *.aspx file (i use webformviewengine) change @page
directive:
<%@ page language="c#" masterpagefile="~/site.master" inherits="me.viewpage2<me.formmodel,me.formdata>" %>
i'll admit 2 generic type specifiers make bit fiddly, it's need set once.
then in each controller it's matter of doing this:
public actionresult edit() { formdata data = new formdata(); data.somestronglytypedfield = "foo"; this.viewdata = data; }
in each view can benefit strongly-typed view data:
<p><%= viewdata.somestronglytypedfield %></p>
and because viewdata not part of model there's separation of concerns when dealing post-submitted data , benefits of automatic binding:
[httppost] public actionresult edit(editmodel model) { if( !modelstate.isvalid ) { // see how can return model object without modifying it. need re-create view data. formdata data = new formdata(); data.somestronglytypedfield = "foo"; this.viewdata = data; return view( model ); } // persist db here return redirecttoaction("view"); }
in practice use common basecontroller class handles automatic creation , setting of viewdata objects don't need 3 lines (formdata data...
etc) in every action.
Comments
Post a Comment