欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

.NET Core开发日志——视图与页面

程序员文章站 2022-06-10 13:02:15
当一个Action完成它的任务后,通常需要返回一个实现IActionResult的对象,而最常见的就是View或者ViewResult,所谓的视图对象。那么视图与最终所看到的页面之间的联系又是怎样形成的,这便是本文想要探讨的问题。 在ResourceInvoker类之中,可以找到下列的代码。这些代码 ......

当一个action完成它的任务后,通常需要返回一个实现iactionresult的对象,而最常见的就是view或者viewresult,所谓的视图对象。那么视图与最终所看到的页面之间的联系又是怎样形成的,这便是本文想要探讨的问题。

在resourceinvoker类之中,可以找到下列的代码。这些代码是对返回结果——iactionresult的进一步处理。

case state.resultinside:
    {
        ...

        var task = invokeresultasync(_result);
        if (task.status != taskstatus.rantocompletion)
        {
            next = state.resultend;
            return task;
        }

        goto case state.resultend;
    }

protected async task invokeresultasync(iactionresult result)
{
    var actioncontext = _actioncontext;

    _diagnosticsource.beforeactionresult(actioncontext, result);
    _logger.beforeexecutingactionresult(result);

    try
    {
        await result.executeresultasync(actioncontext);
    }
    finally
    {
        _diagnosticsource.afteractionresult(actioncontext, result);
        _logger.afterexecutingactionresult(result);
    }
}

iactionresult接口的实现类viewresult中会调用viewresultexecutor类的方法。

public override async task executeresultasync(actioncontext context)
{
    ...

    var executor = context.httpcontext.requestservices.getrequiredservice<iactionresultexecutor<viewresult>>();
    await executor.executeasync(context, this);
}

viewresultexecutor类里则需要先通过razorviewengine类找到对应的视图。

public async task executeasync(actioncontext context, viewresult result)
{
    ...

    var viewengineresult = findview(context, result);
    viewengineresult.ensuresuccessful(originallocations: null);

    var view = viewengineresult.view;
    using (view as idisposable)
    {

        await executeasync(
            context,
            view,
            result.viewdata,
            result.tempdata,
            result.contenttype,
            result.statuscode);
    }

    ...
}

razorviewengine类返回的结果是razorview对象。注意其内部已包含了irazorpage对象。

public viewengineresult getview(string executingfilepath, string viewpath, bool ismainpage)
{
    ...

    var cacheresult = locatepagefrompath(executingfilepath, viewpath, ismainpage);
    return createviewengineresult(cacheresult, viewpath);
}

public viewengineresult findview(actioncontext context, string viewname, bool ismainpage)
{
    ...

    var cacheresult = locatepagefromviewlocations(context, viewname, ismainpage);
    return createviewengineresult(cacheresult, viewname);
}

private viewengineresult createviewengineresult(viewlocationcacheresult result, string viewname)
{
    ...

    var page = result.viewentry.pagefactory();

    var viewstarts = new irazorpage[result.viewstartentries.count];
    for (var i = 0; i < viewstarts.length; i++)
    {
        var viewstartitem = result.viewstartentries[i];
        viewstarts[i] = viewstartitem.pagefactory();
    }

    var view = new razorview(this, _pageactivator, viewstarts, page, _htmlencoder, _diagnosticsource);
    return viewengineresult.found(viewname, view);
}

找到视图后,viewresultexecutor再调用其父类viewexecutor的executeasync方法。其内部将调用razorview类的renderasync方法。

protected async task executeasync(
    viewcontext viewcontext,
    string contenttype,
    int? statuscode)
{
    ...

    var response = viewcontext.httpcontext.response;

    responsecontenttypehelper.resolvecontenttypeandencoding(
        contenttype,
        response.contenttype,
        defaultcontenttype,
        out var resolvedcontenttype,
        out var resolvedcontenttypeencoding);

    response.contenttype = resolvedcontenttype;

    if (statuscode != null)
    {
        response.statuscode = statuscode.value;
    }

    using (var writer = writerfactory.createwriter(response.body, resolvedcontenttypeencoding))
    {
        var view = viewcontext.view;

        var oldwriter = viewcontext.writer;
        try
        {
            viewcontext.writer = writer;

            diagnosticsource.beforeview(view, viewcontext);

            await view.renderasync(viewcontext);

            diagnosticsource.afterview(view, viewcontext);
        }
        finally
        {
            viewcontext.writer = oldwriter;
        }

        // perf: invoke flushasync to ensure any buffered content is asynchronously written to the underlying
        // response asynchronously. in the absence of this line, the buffer gets synchronously written to the
        // response as part of the dispose which has a perf impact.
        await writer.flushasync();
    }
}

razorview类中可以看到其核心的处理与irazorpage的executeasync方法紧密相关。

public virtual async task renderasync(viewcontext context)
{
    ...

    _bufferscope = context.httpcontext.requestservices.getrequiredservice<iviewbufferscope>();
    var bodywriter = await renderpageasync(razorpage, context, invokeviewstarts: true);
    await renderlayoutasync(context, bodywriter);
}

private async task<viewbuffertextwriter> renderpageasync(
    irazorpage page,
    viewcontext context,
    bool invokeviewstarts)
{
    var writer = context.writer as viewbuffertextwriter;
    ...

    // the writer for the body is passed through the viewcontext, allowing things like htmlhelpers
    // and viewcomponents to reference it.
    var oldwriter = context.writer;
    var oldfilepath = context.executingfilepath;

    context.writer = writer;
    context.executingfilepath = page.path;

    try
    {
        if (invokeviewstarts)
        {
            // execute view starts using the same context + writer as the page to render.
            await renderviewstartsasync(context);
        }

        await renderpagecoreasync(page, context);
        return writer;
    }
    finally
    {
        context.writer = oldwriter;
        context.executingfilepath = oldfilepath;
    }
}

private async task renderpagecoreasync(irazorpage page, viewcontext context)
{
    page.viewcontext = context;
    _pageactivator.activate(page, context);

    _diagnosticsource.beforeviewpage(page, context);

    try
    {
        await page.executeasync();
    }
    finally
    {
        _diagnosticsource.afterviewpage(page, context);
    }
}

但当查找irazorpage接口的实现。从razorpagebaserazorpage,再到razorpage<tmodel>,这些都只是抽象类,且都没有对executeasync方法有具体实现。

源码里找不到进一步的实现类,线索到这里断开了。

这时可以建立一个mvc的应用程序,编译后找到它的bin目录,会看到其中包含一个*.view.dll文件。

.NET Core开发日志——视图与页面

使用反编译软件,比如dotpeek,查看里面的内容,会找到一些由cshtml文件生成的类。

.NET Core开发日志——视图与页面

以其中views_home_index为例,其实际上为razorpage<tmodel>的一个实现类。

.NET Core开发日志——视图与页面

它内部的executeasync方法正是生成页面内容的关键。

.NET Core开发日志——视图与页面

因为是vs模板自动生成的页面,上面的代码十分冗杂。为了更清晰地检查核心的代码,不妨减少下页面的复杂度。

把index.cshtml文件内容改成如下:

@{
    viewdata["title"] = "home page";
    layout = null;
}

<p>hello world!</p>

再次编译后,可以看到executeasync方法的内容变成了下面的样子:

public virtual async task executeasync()
{
  ((viewdatadictionary) this.get_viewdata()).set_item("title", (object) "home page");
  ((razorpagebase) this).set_layout((string) null);
  ((razorpagebase) this).begincontext(65, 21, true);
  ((razorpagebase) this).writeliteral("\r\n<p>hello world!</p>");
  ((razorpagebase) this).endcontext();
}

不难看出,最终展现的页面内容便是通过razorpagebase类的writeliteral方法生成的。