[原]ASP.NET MVC親自指定Action參數值

來源:互聯網
上載者:User

文章名字好難起哦,既想能清楚的表達本文的主旨,又想短小精悍,真難。

為啥要“親自”呢?我想表達的意思是,在自已寫的程式中自已控制一切,這就叫親自。說起這個詞,還有一個典故(真人真事,如果雷同,實屬巧合):

在我上高三時,四班有一個位同學姓黃,名**,他以前在三中,後來轉學到一中的。該黃姓同學一直在追求一種境界,到底是一種什麼樣的境界,很難描述...他可以在課堂上肆無忌憚的排放腹中廢氣,也可以在毫無徵兆的情況下打個方圓30m之內其他房間可以聽到的噴嚏,甚至可以和校長開玩笑,一次,他課間去噓噓,本來是不允許學生去教師的WC的,但他是無視這種規定的,他先到的,正在放水時校長進來了,他就和校長打了個招呼:“高校長,您親自來上廁所了?”,校長被憋的竟只得"唔,唔"搪塞過去...

很是懷念校園的時光啊

進入正題:

為啥需要“親自”呢,這得說明來龍去脈:我的本意是做一個RESTful服務,自已寫了一個Atom的Client,使用WebRequest向伺服器提交資料,當然,格式是Atom10的,在伺服器端使用Request把Client傳上來的資料拿出來,如下虛擬碼所示:

public ActionResult Create(){    var entry = GetFromRequest();    //...}

這樣好嗎?剛寫好時覺得不錯,但第二眼就覺得不好了,為啥,如果這個entry是從Create方法的參數中傳來的該多好啊,想起以前在學習WCF時遇到的一個問題,使用Atom10FeedFormatter類就可以在參數中獲得這個entry的執行個體了,何樂不為呢,於是代碼變成這樣:

public ActionResult Create(Atom10FeedFormatter<LogEntry> log){    //...}

不幸的是,按照之前經驗,在該方法內部可以通過log.Item就獲得從Client傳來的實體了,但是在這裡發現log.Item居然為null。

這才發現原來MVC和WCF Syndication的機制是不同的啊。

通過閱讀MVC的原始碼發現,ControllerActionInvoker中一個方法GetParameterValues,這個方法擷取Action的參數列表,然後把根據一些策略產生對應的參數值,但是這個GetParameterValues似乎只能通過重載才能達到我想要的目的,達到我的目的又如何保證不影響MVC本來的意圖呢?還是先試試不重載,看有沒有輕量級的解決方案吧。

又想起來MVC中Filter不是幾乎無所不能麼,要麼來個ActionFitler好了,於是定義了一個類型:

[AttributeUsage(AttributeTargets.Method)]public class AtomEntryConvertAttribute : Attribute, IActionFilter{    public void OnActionExecuting(ActionExecutingContext filterContext)     {        //...     }    public void OnActionExecuted(ActionExecutedContext filterContext) { }}

試圖在OnActionExecuting中從Request中讀取資料,結果很快發現了問題,Action都執行了,該屬性類別的方法還沒有執行,仔細查看MVC的原始碼,發現了問題所在:

        protected virtual ActionExecutedContext InvokeActionMethodWithFilters(ControllerContext controllerContext, IList<IActionFilter> filters, ActionDescriptor actionDescriptor, IDictionary<string, object> parameters) {            ActionExecutingContext preContext = new ActionExecutingContext(controllerContext, actionDescriptor, parameters);            Func<ActionExecutedContext> continuation = () =>                new ActionExecutedContext(controllerContext, actionDescriptor, false /* canceled */, null /* exception */) {                    Result = InvokeActionMethod(controllerContext, actionDescriptor, parameters)                };            // need to reverse the filter list because the continuations are built up backward            Func<ActionExecutedContext> thunk = filters.Reverse().Aggregate(continuation,                (next, filter) => () => InvokeActionMethodFilter(filter, preContext, next));            return thunk();        }

Action的調用在ActionMethodFitler之前...汗啊,真是不長記性,上次就在這裡被“騙”一次,時間不長居然又忘了這茬了。

又回到GetParameterValues方法...

(此處省去2小時的思考,嘗試過程)

有一點結論了,如果要實現輕量級“非侵入”式的操作,以M$的習慣,一般是使用Attribute或者反射的,這裡用反射似乎不妥,那麼就應該在Entry類型或是參數本身上應用Attribute比較合適,根據M$的命名習慣,這種事情應該使用諸如Custom、Convert、Parameter之類的名稱,帶著這個思路一找,果然發現了一個類型CustomModelBinderAttribute。

光是這個類型名,就感覺是幹這個事情的,自訂 模型 綁定 特性 ,看起來像了,該類型中有一個公用方法:

public abstract IModelBinder GetBinder();

剛才在讀MVC原始碼時就發現這個IModelBinder介面大量使用在GetParameterValues方法中,差不多了,再看IModelBinder的定義:

object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext);

看定義就覺得像是從controllerContext和bindingContext中產生一個參數值的。

直接建立一個CustomModelBinderAttribute的子類:

    [AttributeUsage(AttributeTargets.Parameter)]    public class AtomEntryParameterConvertAttribute : CustomModelBinderAttribute    {        public Type EntryType { get; private set; }        public AtomEntryParameterConvertAttribute(Type entryType)            :base()        {            this.EntryType = entryType;        }        public override IModelBinder GetBinder()        {            return new AtomEntryConvertModelBinder(this.EntryType);        }    }

再建立一個IModelBinder的實現:

    internal class AtomEntryConvertModelBinder : IModelBinder    {        public Type EntryType { get; private set; }        internal AtomEntryConvertModelBinder(Type entryType)        {            this.EntryType = entryType;        }        public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)        {            var result = AtomServiceHelper.GetDataFromEntry(controllerContext.HttpContext.Request, this.EntryType, bindingContext.ModelType);            return result;        }    }

這裡我要稍說明下,其實我在Action的參數中收到的不是直接從用戶端上傳來的SyndicationItem類型的子類AtomEntry,AtomEntry只是Client和Server互動時的基於Atom協議的資料結構,它派生自SyndicationItem類,真正在Server上使用的是實體類,在我的例子中:

public class Log {}

Log類型是真正服務端業務層、資料層使用的實體類型

public class LogEntry : AtomEntry {}public class AtomEntry : SyndicationItem {}

LogEntry類型是Client與Server互動的資料格式

因此,我不僅需要從Client上把LogEntry的執行個體傳到Server,而且還要在Server上的Action中使用參數直接獲得Log類型的執行個體,當然Log和LogEntry的定義是一個策略,或者說它們之間是有約定的,沒有約定,它倆也完不成轉換,我喜歡約定甚於配置。

AtomServiceHelper.GetDataFromEntry(controllerContext.HttpContext.Request, this.EntryType, bindingContext.ModelType)

正是AtomServiceHelper類讀取Request中的資料,然後將EntryType類型的執行個體轉換為參數類型的執行個體。

其實對於IModuleBinder我也沒有查看MSDN,只是感覺就是這種用法,對不對還沒有做實驗,於是寫個例子:

        [HttpPost]        [ServiceError]        public ActionResult Create([AtomEntryParameterConvert(typeof(LogEntry))]LogRecord log)        {            var logMng = new LogManager();            logMng.CreateLog(log);            return new EmptyResult();        }

運行,yeah!果然如願得到了來自Client的資料

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.