Tuesday, December 8, 2015

Generate client views with jQuery templates in ASP.NET MVC. RsRender Engine

Здравствуйте, уважаемые читатели. Сегодня мы с вами продолжим погружение в мир web-разработки программного обеспечения. А рассмотрим мы интересную и актуальную тему, которая редко затрагивается в книгах по ASP.NET MVC. Сегодня мы разберем пример того, как можно использовать генерацию вашего кода отображения со стороны клиента. Итак, в чем это дает преимущество – как минимум в объемах, пересылаемых данных, так как большинство движков работает с json-форматом данных. Мы также проанализируем работу с jQuery Templates. Команда jQuery написала плагин, который так и называется: jQuery Templates. Но он так и не вышел с беты, и над ним прекратили работу, да и сами разработчики предлагаю посмотреть в сторону JsRender, что и предлагаю сделать. Если перейти на сайт JsRender, то можно увидеть, что JsRender предлагает интересную связку с JsViews, которая позволит работать с вашими данными, используя паттерн MVVM и MVP. На момент написания статьи существовало много движков, о части которых я даже никогда не слышал, но некоторые из них мы можем перечислить. Это knockoutjs, JsViews, Kendo UI Templates, Angular, Pure и другие. Например, можете здесь посмотреть список из 10 таких движков: 10 JavaScript and jQuery Templates Engines.  
Сегодняшняя задача следующая: сделать выбор списка машин и по запросу с сервера загружать детальную информацию по них. Не так давно я писал подобный пример в статье "Использование Autocomplete для своих приложений ASP.NET MVC5", так что сегодня будет что-то подобное. Как минимум, мы переделаем тот проект к сносному виду и будем использовать JsRender для генерации UI. Классический пример работы генерации представления в ASP.NET MVC показан на рисунке ниже.
Когда на ринг выходит JsRender, в идеале диаграмма должна иметь следующий вид:
Но так как мы живем не в идеальном мире, да и мне нравится то, как Razor генерирует нам представления, поэтому подход будет следующим:
Эта диаграмма нуждается в пояснении. Дело в том, что проще сгенерировать пустую страницу вначале, которая будет возвращена через ViewResult, а затем все данные получать через ajax запросы в формате json. Такой подход используют разработчики, которые на клиентской части используют knockout.js или angular js.  Мы же с вами ничем не хуже, да и сами, наверное, используете такой подход, поэтому предлагаю не отходить от традиций. Тем более, если традиции можно улучшить или скомбинировать с другими. 
Достаточно теории; приступаем к практике. Первым делом создадим новый проект ASP.NET Web Application, который назовем “JsrenderSample”.
Затем выберем готовый шаблон MVC, как показано на рисунке ниже.
Суть нашего проекта будет заключаться в следующем. На нашу html страницу мы будем выводить информацию о самых дорогих автомобилях и их характеристики. Для того чтобы хранить где-то информацию об автомобилях, нам понадобится соответственная модель данных. Для этого зайдем в папку Models и добавим туда новый класс Car.
public class Car
{
    public int Id { get; set; }
    public string Name { get; set; }
    public double Price { get; set; }
    public int Speed { get; set; }
    public double Acceleration { get; set; }
    public int Power { get; set; }
    public double Displacement { get; set; }
    public int Weight { get; set; }
}
Теперь нам нужно откуда-то брать эту информацию. У меня она хранится в файле cars.txt, и вы сможете скачать этот файл себе по ссылке в конце статьи. Но для того чтобы ваш пример работал, вы можете создать свой файл с информации, которая приведена ниже (ее просто нужно скопировать и поместить в текстовый файл), и положить этот файл в папку App_Data, так как в эту папку у вас точно будет доступ.
car_id, car_name, cost, top_speed,0_100_kph,power_bhp,displacement, weight,
1,"Ferrari 250 GTO", 52000000,280,6.1,302,3,1100,
2,"Ferrari   250 Testa Rossa", 16400000,259,6,300,3,800,
3,"Jaguar XJ13", 15000000,274,3.4,509,5,998,
4,"Mercedes-Benz SLR McLaren 999 Red Gold Dream Ueli Anliker", 10000000,340,3,999,5.4,1800,
5,"Ferrari 330 P4", 9000000,338,5,450,4,875,
6,"Maybach Exelero", 8000000,351,4.4,700,5.9,2600,
7,"Rolls-Royce Hyperion Pininfarina", 6000000,250,5.6,460,6.7,2650,
Просто скопируйте в отдельный файл информацию выше, и пример у вас будет работать. Затем перейдем в наш контроллер HomeController и добавим приватный метод GetAllCars(), который достанет нам все необходимые данные. В этот метод мы добавим action filter OutputCache, для того чтобы кешировать получение списка GetAllCars. Так как это список изменяться особо не будет, мы добавили кеширование на 5 минут.
[OutputCache(Duration = 300)]
private List<Car> GetAllCars()
{
    var path = HttpContext.Server.MapPath("~/App_Data/cars.txt");
    var cars = System.IO.File.ReadAllLines(path)
        .Skip(1)
        .Select(x => x.Split(new[] { ',' }, StringSplitOptions.None))
        .Select(item =>
        {
            return new Car()
            {
                Id = int.Parse(item[0]),
                Name = item[1].Trim(new[] { '"' }),
                Price = double.Parse(item[2]),
                Speed = int.Parse(item[3]),
                Acceleration = double.Parse(item[4]),
                Power = int.Parse(item[5]),
                Displacement = double.Parse(item[6]),
                Weight = int.Parse(item[7])
            };
        })
        .ToList();

    return cars;
}
Затем добавим метод, который назовем Autocomplete, чтобы он подгружал не все данные сразу, а только часть.
public JsonResult Autocomplete(string term)
{
    var cars = GetAllCars();
    var result = cars.Where(s => s.Name.ToLower().Contains
                    (term.ToLower())).Select(w => w.Name).ToList();
    return Json(result, JsonRequestBehavior.AllowGet);
}
Приступим сейчас к реализации нашего примера. Для этого откроем наше представление Index.cshtml и подправим его следующим образом:
@{
    ViewBag.Title = "Home Page";
}

<link rel="stylesheet" href="http://code.jquery.com/ui/1.11.4/themes/smoothness/jquery-ui.css">
<script src="http://code.jquery.com/jquery-1.10.2.js"></script>
<script src="http://code.jquery.com/ui/1.11.4/jquery-ui.js"></script>

<div class="form-control-static">
    <div class="form-group">
        <label for="autocompleteCar">Enter car name: </label>
        <input id="autocompleteCar" class="form-control">
    </div>
    <div class="form-group">
        <input type="submit" id="submit" class="btn btn-info"/>
    </div>
</div>

<script type="text/javascript">
    $("#autocompleteCar").autocomplete({
        source: '@Url.Action("Autocomplete", "Home")',
        minLength: 1,
        width: 200
    });
</script>
Для заполнения текста в контроле мы использовали плагин jQuery Autocomplete, но это не важно в нашем случае. Просто скопируйте код и запустите пример, чтобы убедиться, что он сейчас работает. Ниже на рисунке показано, как это выглядит у меня.
Самое время реализовать выборку деталей по каждой машине и показать их на клиенте по нажатию на кнопку submit. Для начала нам нужно загрузить jsrender.js скрипты. Сделать это можно отсюда: JsRender, JsViews and JsObservable Downloads вручную, либо использовать HotGlue.Template.JsRender, который работает с движком JsRender, потому что на момент подготовки статьи написание NuGet пакета только планировалось Create a NuGet package #211. Загружаем себе jsrender c сайта и добавляем его в нашу папку Scripts.
Следующим делом поправим немного BundleConfig.cs, чтобы сделать загрузку при старте нашего приложения. В самый конец функции RegisterBundles добавляем следующую строку:
bundles.Add(new ScriptBundle("~/bundles/custom").Include(
                                "~/Scripts/jsRender.js"));
Переходим в папку Shared и открываем на редактирование файл _Layout.cshtml. Ищем в самом конце секцию
@RenderSection("scripts", required: false)
И добавляем перед ней рендеринг нашего бандла.
@Scripts.Render("~/bundles/custom")
У меня это выглядит следующим образом:
@Scripts.Render("~/bundles/jquery")
@Scripts.Render("~/bundles/bootstrap")
@Scripts.Render("~/bundles/custom")
@RenderSection("scripts", required: false)
Затем нужно реализовать в контроллере HomeController функцию, которая будет доставать детальную информацию по конкретной машине. Назовем эту функцию GetCarDetails.
public JsonResult GetCarDetails(string carName)
{
    var cars = GetAllCars();
    var car = cars.FirstOrDefault(x => x.Name == carName);
    return Json(car, JsonRequestBehavior.AllowGet);
}
Для полной проверки приведу весь листинг класса HomeController.
public class HomeController : Controller
{
    public ActionResult Index()
    {
        return View();
    }

    public JsonResult Autocomplete(string term)
    {
        var cars = GetAllCars();
        var result = cars.Where(s => s.Name.ToLower().Contains
                        (term.ToLower())).Select(w => w.Name).ToList();
        return Json(result, JsonRequestBehavior.AllowGet);
    }

    [OutputCache(Duration = 300)]
    private List<Car> GetAllCars()
    {
        var path = HttpContext.Server.MapPath("~/App_Data/cars.txt");
        var cars = System.IO.File.ReadAllLines(path)
            .Skip(1)
            .Select(x => x.Split(new[] { ',' }, StringSplitOptions.None))
            .Select(item =>
            {
                return new Car()
                {
                    Id = int.Parse(item[0]),
                    Name = item[1].Trim(new[] { '"' }),
                    Price = double.Parse(item[2]),
                    Speed = int.Parse(item[3]),
                    Acceleration = double.Parse(item[4]),
                    Power = int.Parse(item[5]),
                    Displacement = double.Parse(item[6]),
                    Weight = int.Parse(item[7])
                };
            })
            .ToList();

        return cars;
    }

    public JsonResult GetCarDetails(string carName)
    {
        var cars = GetAllCars();
        var car = cars.FirstOrDefault(x => x.Name == carName);
        return Json(car, JsonRequestBehavior.AllowGet);
    }

    public ActionResult About()
    {
        ViewBag.Message = "Your application description page.";

        return View();
    }

    public ActionResult Contact()
    {
        ViewBag.Message = "Your contact page.";

        return View();
    }
}
Продолжим правку файла Index.cshtml с папки Views\Home. Добавим новый div, который будет отображать наш результат.
<div class="form-group" id="result-box">
</div>
Затем нужно сгенерировать темплейт, который будет отображать всю необходимую информацию. Для этого сразу после нашего предыдущего блока div добавим следующий шаблон:
<script id="template" type="text/x-jsrender">
    <label for="Name">Name: </label>
    <label id="Name" class="form-control">{{:Name}}</label>
    <label for="Price">Price: </label>
    <label id="Price" class="form-control">{{:Price}}</label>
    <label for="Speed">Price: </label>
    <label id="Speed" class="form-control">{{:Speed}}</label>
    <label for="Acceleration">Acceleration: </label>
    <label id="Acceleration" class="form-control">{{:Acceleration}}</label>
    <label for="Power">Power: </label>
    <label id="Power" class="form-control">{{:Power}}</label>
    <label for="Displacement">Displacement: </label>
    <label id="Displacement" class="form-control">{{:Displacement}}</label>
    <label for="Weight">Weight: </label>
    <label id="Weight" class="form-control">{{:Weight}}</label>
</script>
Генерацию шаблонов мы можем разнести по разным файлам, что очень удобно для больших приложений. Теперь время реализовать обработчик кнопки submit, который будет выполнять ajax запрос, который мы ставим для нашего движка. Вот так все просто. Ниже приведена реализация данного обработчика.
$("#submit").click(function (evt) {
    evt.preventDefault();

    $.ajax({
        url: '@Url.Action("GetCarDetails", "Home")',
        type: "POST",
        dataType: "json",
        data: { carName: $("#autocompleteCar").val() },
        success: function (data) {
            $("#result-box").html(
                $("#template").render(data)
            );
        },
        error: function (xhr) {
            alert(xhr.responseText);
        }
    });
});
Теперь для проверки рассмотрим, как выглядит код всего файла Index.cshtml, чтобы убедиться, что мы ничего не пропустили.
@{
    ViewBag.Title = "Home Page";
}

<link rel="stylesheet" href="http://code.jquery.com/ui/1.11.4/themes/smoothness/jquery-ui.css">
<script src="http://code.jquery.com/jquery-1.10.2.js"></script>
<script src="http://code.jquery.com/ui/1.11.4/jquery-ui.js"></script>

<div class="form-control-static">
    <div class="form-group">
        <label for="autocompleteCar">Enter car name: </label>
        <input id="autocompleteCar" class="form-control">
    </div>
    <div class="form-group">
        <input type="submit" id="submit" class="btn btn-info"/>
    </div>
</div>

<div class="form-group" id="result-box">
</div>

<script id="template" type="text/x-jsrender">
    <label for="Name">Name: </label>
    <label id="Name" class="form-control">{{:Name}}</label>
    <label for="Price">Price: </label>
    <label id="Price" class="form-control">{{:Price}}</label>
    <label for="Speed">Price: </label>
    <label id="Speed" class="form-control">{{:Speed}}</label>
    <label for="Acceleration">Acceleration: </label>
    <label id="Acceleration" class="form-control">{{:Acceleration}}</label>
    <label for="Power">Power: </label>
    <label id="Power" class="form-control">{{:Power}}</label>
    <label for="Displacement">Displacement: </label>
    <label id="Displacement" class="form-control">{{:Displacement}}</label>
    <label for="Weight">Weight: </label>
    <label id="Weight" class="form-control">{{:Weight}}</label>
</script>

<script type="text/javascript">
    $("#autocompleteCar").autocomplete({
        source: '@Url.Action("Autocomplete", "Home")',
        minLength: 1,
        width: 200
    });

    $("#submit").click(function (evt) {
        evt.preventDefault();

        $.ajax({
            url: '@Url.Action("GetCarDetails", "Home")',
            type: "POST",
            dataType: "json",
            data: { carName: $("#autocompleteCar").val() },
            success: function (data) {
                $("#result-box").html(
                  $("#template").render(data)
                );
            },
            error: function (xhr) {
                alert(xhr.responseText);
            }
        });
    });
</script>
Запустим наш проект и проверим, что данные возвращаются успешно.
Как видите, все неплохо получилось. Теперь немного допилим наш вариант и все-таки выведем весь список машин из стоимости в какой-либо div. Для начала добавим новый GetCars в наш контроллер.
public JsonResult GetCars()
{
    var cars = GetAllCars();
    return Json(cars, JsonRequestBehavior.AllowGet);
}
Теперь перейдем к реализации представления. Как и в предыдущем примере, добавим сначала новый блок div и шаблон для отображения.
<div id="carList">
</div>

<script id="listCarTemplate" type="text/x-jsrender">
    <div>
        {{:#index+1}}: <b>{{>Name}}</b> ({{>Price}} $)
    </div>
</script>
Теперь осталось добавить ajax метод, который загрузит нам список машин.
$.ajax({
    url: '@Url.Action("GetCars", "Home")',
    type: "GET",
    dataType: "json",
    success: function (data) {
        $("#carList").html(
            $("#listCarTemplate").render(data)
        );
    },
    error: function (xhr) {
        alert(xhr.responseText);
    }
});
Чуть позже мы разберем весь синтаксис более детально. Перепроверяем наш код, чтобы убедиться в том, что мы ничего не пропустили.
@{
    ViewBag.Title = "Home Page";
}

<link rel="stylesheet" href="http://code.jquery.com/ui/1.11.4/themes/smoothness/jquery-ui.css">
<script src="http://code.jquery.com/jquery-1.10.2.js"></script>
<script src="http://code.jquery.com/ui/1.11.4/jquery-ui.js"></script>

<div class="form-control-static">
    <div class="form-group">
        <label for="autocompleteCar">Enter car name: </label>
        <input id="autocompleteCar" class="form-control">
    </div>
    <div class="form-group">
        <input type="submit" id="submit" class="btn btn-info"/>
    </div>
</div>

<div class="form-group" id="result-box">
</div>

<script id="template" type="text/x-jsrender">
    <label for="Name">Name: </label>
    <label id="Name" class="form-control">{{:Name}}</label>
    <label for="Price">Price: </label>
    <label id="Price" class="form-control">{{:Price}}</label>
    <label for="Speed">Price: </label>
    <label id="Speed" class="form-control">{{:Speed}}</label>
    <label for="Acceleration">Acceleration: </label>
    <label id="Acceleration" class="form-control">{{:Acceleration}}</label>
    <label for="Power">Power: </label>
    <label id="Power" class="form-control">{{:Power}}</label>
    <label for="Displacement">Displacement: </label>
    <label id="Displacement" class="form-control">{{:Displacement}}</label>
    <label for="Weight">Weight: </label>
    <label id="Weight" class="form-control">{{:Weight}}</label>
</script>

<div id="carList">
</div>

<script id="listCarTemplate" type="text/x-jsrender">
    <div>
        {{:#index+1}}: <b>{{>Name}}</b> ({{>Price}} $)
    </div>
</script>

<script type="text/javascript">
    $("#autocompleteCar").autocomplete({
        source: '@Url.Action("Autocomplete", "Home")',
        minLength: 1,
        width: 200
    });

    $.ajax({
        url: '@Url.Action("GetCars", "Home")',
        type: "GET",
        dataType: "json",
        success: function (data) {
            $("#carList").html(
              $("#listCarTemplate").render(data)
            );
        },
        error: function (xhr) {
            alert(xhr.responseText);
        }
    });

    $("#submit").click(function (evt) {
        evt.preventDefault();

        $.ajax({
            url: '@Url.Action("GetCarDetails", "Home")',
            type: "POST",
            dataType: "json",
            data: { carName: $("#autocompleteCar").val() },
            success: function (data) {
                $("#result-box").html(
                  $("#template").render(data)
                );
            },
            error: function (xhr) {
                alert(xhr.responseText);
            }
        });
    });
</script>
Теперь запустим наш пример, чтобы проверить, что все работает.
Получили в итоге симпатичный список машин и цену каждой машины. Теперь детальнее рассмотрим на то, что за шаблон у нас генерируется.
  1. Шаблон id=listCarTemplate будет просто интерпретирован как текст, и браузер его обрабатывать не будет.
  2. {{:#index+1}} позволяет обратиться к свойствам объекта. Так как мы указали решетку, то мы получим индекс списка, который начинается с 0, поэтому мы добавили + 1. Если не использовать #, то мы бы просто искали переменную с именем index, как в предыдущем нашем примере.
  3. {{>Name}} и {{>Price}} позволяет обратиться к свойствам Name и Price нашего класса Car, а треугольная скобка вначале говорит о том, что строка будет кодированная, то есть, будут видны теги.
Что такое div и что делает функция render, думаю, не стоит объяснять. Просто если вы не знаете, зачем нужен div, то на процентов 90 вы ничего со статьи не поняли. И тут уже нужно возвращаться к основам html, что не является идеей данной статьи.
Если вы работали с angular js, то такой синтаксис не должен у вас вызывать никакого дискомфорта, тем более, что он подобен angular js.
<div class="row">
    <div class="col-lg-12">
        <h1 class="page-header">
            {{name}}({{surname}})
        </h1>
    </div>
</div>
Синтаксис позволяет использовать цикл for,
{{for cars}}
    <div>{{:Name}}</div>
{{/for}}
операторы if/else, операторы сравнения и многое другое. Не вижу особого смысла приводить полную документацию, так как вы сами с ней можете ознакомиться JsRender Quickstart. Это не займет много времени: там одна страничка текста. Чтобы начать использовать все, что там описано, достаточно полчаса времени.
Итоги
Основной идеей этой статьи было показать, как работать с генерацией темплейтов на стороне клиента, передавая туда json результат, и я думаю, мы с вами справились с поставленной задачей. Если вы работаете или работали с angular или knockout, то подобный синтаксис для вас не в новинку. Он часто реализуется по-другому и имеет свою структуру, но суть его остается той же. Спасибо за внимание. Буду рад ответить на ваши вопросы в комментариях к статье.
Статьи, которые пригодятся и будут интересны вам для изучения данной области:


Исходники к статье: JsRender sample

Sunday, December 6, 2015

ASP.NET Architecture. Deep dive

Здравствуйте, уважаемые читатели. Темой сегодняшней статьи будет низкоуровневая архитектура ASP.NET. Наверное, многих интересует принцип работы машины ASP.NET, когда они вводят url адрес в строку браузера. В интернете есть немного информации, которая, в основном, очень устарела, или покрывает нужную область лишь частично. Есть несколько статей, с которыми я детально ознакомился , прежде чем решился на эту статью:
Топ-статья из всех вышеперечисленных – последняя. Даже учитывая тот факт, что год написания этой статьи – 2008, особо ничего не изменилось. Благо, сейчас вы можете все это легко проверить и сами попробовать, так как Microsoft открыла исходники .NET Framework, библиотеки которого вы можете посмотреть онлайн по ссылке referencesource.microsoft.com. В общем, я и смотрел, как это все работает изнутри, с помощью открытых исходников. К сожалению, не существует способа лучше узнать предметную область, чем в нее окунуться. Чем мы, собственно, и займемся сегодня. 
Начнем с краткого описания процессов, которые происходят, когда вы вводите ваш url в строку браузера. Первым делом ваш запрос перехватывается с помощью системной библиотеки http.sys, анализируется и пересылается дальше пулу приложений (каждым пулом приложений управляет процесс w3wp.exe). Дальше наш процесс w3wp.exe изучает URL запроса и загружает ASP.NET ISAPIaspnet_isapi.dll”. ASP.NET ISAPI загружает среду выполнения HTTP
На этом моменте остановимся подробнее. Загрузка среды выполнения HTTP начинается с вызова метода IsapiRuntime.ProcessRequest(), который вызывается с неуправляемого кода. Этот метод запускает метод, который является точкой входа среды выполнения – HttpRuntime.ProcessRequest. Данный метод внутри вызывает метод ProcessRequestInternal, в который передает экземпляр класса HttpWorkerRequest. Класс HttpWorkerRequest, по сути, является рабочей лошадкой HttpContext. Суть метода ProcessRequestInternal – это создать экземпляр HttContext и создает инстанс приложения с пула (это наша точка входа класс HttpApplication). Как это происходит, показано ниже.
Сначала, как мы видим, происходит создание класса HttpContext. Этот класс существует на протяжении всей жизни запроса, и его можно получить через статическое свойство HttpContext.Current. Суть этого объекта заключается в том, что он содержит в себе ссылки и объекты, доступ к которым мы можем получить, пока жив запрос (Response, Request, Cache и т.д.). Как только функция создала HttpContext, в том же методе происходит создание HttpAppication и связывание каждого запроса с данным объектом HttpApplication. Создается этот класс с помощью абстрактной фабрики, которая называется HttpAppicationFactory.
Почему-то многие авторы статей сразу после того как рассказали о том, как создается экземпляр HttpApplication, начинают переходить к конвейеру HTTPкоторый называется так, потому что состоит из модулей HttpModules, которые перехватываю запрос на пути к HttpHandlers. Это основная рабочая лошадка ASP.NET, поскольку благодаря этим модулям ваше приложение и работает. Разъясню, как эти модули инициализируются, чтобы все стало на свои места. 
В методе HttpAppicationFactory.GetApplicationInstance() есть множество разных методов внутри. Но один из них заслуживает отдельного рассмотрения. Это метод HttpApplication.Init. Его суть заключается в том, что он запускает подгруздку HttpModules и подписывается на события с этих модулей с помощью рефлексии. Первый этап – погрузить модули с помощью функции InitModule().
Ниже показано, как эти модули загружаются с config файла.
Небольшое отступление. По умолчанию часть модулей прописана в файле, который называется “machine.config”, плюс ко всему, вы можете сами писать кастомные модули и связывать их через web.config. Чуть позже мы остановимся на этом детальнее. Теперь вторая часть, которую мы упустили, – подписка на события.
Там обычный цикл и куча кода внутри foreach, которая в 90% построена на рефлексии.
Давайте немного подытожим информацию по классу HttpApplication. Первое – этот класс является внешним контейнером для конкретного веб-приложения, который связан с классом, определенным в Global.asax. Этот класс создается через фабрику HttpAppicationFactory и передается сразу запросу. Он также служит как контейнер для предопределенных событий, которые возбуждаются в течении жизни запроса, а также как контейнер для HttpModules.
После того, как мы создали HttpApplication, сразу же вызываем метод ProcessRequest для начала обработки. И от на этом этапе у нас в бой вступают HttpModules.
Задача эти модулей самая различная. Например, аутентификация, управление состоянием и модули кеширования, управление маршрутизацией и другие. И здесь один из самых важных модулей, благодаря которому ваше приложение работает, – это UrlRoutingModule. Его задача является запуск маршрутизации, анализ и парсинг входящего URL и заполнить контекст запроса (request context). Как это работает, показано ниже. У класса Global.asax определен метод
RouteConfig.RegisterRoutes(RouteTable.Routes);
Его задача стоит в том, чтобы построить таблицу маршрутизации и добавить туда нужные пути (System.Web.Routing.RouteTable.Routes). Записи в этой таблице представляют собой шаблон для допустимых Url адресов. Это разные заполнители.
routes.MapRoute(
    name: "Default",
    url: "{controller}/{action}/{id}",
    defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
Смотрим выше на заполнители в параметре url. С каждой записью связан обработчик маршрута – объект, который реализует интерфейс IRouteHandler. У этого интерфейса есть только один метод GetHttpHandler, который возвращает IHttpHandler. Для большинства приложений ASP.NET MVC у нас уже существует обработчик по умолчанию, который называется MvcRouteHandler и знает, как с помощью контекста запроса вызвать соответствующий контролер. Класс MvcRouteHandler возвращает инстанс класса MvcHandler. В этом классе MvcHandler есть метод ProcessRequest, который, используя интерфейс IControllerFactory, создает соответствующий контроллер. По умолчанию интерфейс IControllerFactory реализован в виде класса DefaultControllerFactory. Ниже представлен асинхронный вариант этого метода. Полную реализацию можно посмотреть на github MvcHandler.cs.
Следующее действие заключается в том, что контроллер вызывает метод InvokeAction, передавая детали для выбора метода выполнения. Метод доступен благодаря интерфейсу IActionInvoker. В ASP.NET MVC есть класс, который по умолчанию называется ControllerActionInvoker. У него не особо сложная логика, поэтому вы можете ознакомиться с ней более внимательно. Там есть такой метод, который называется FindAction. Он собирает информацию по всем методам и ищет самый подходящий. Все данные по методам выгребаются через атрибуты, которые получаются через класс ActionDescriptor. После того как метод был найден, в работу вступает DefaultModelBinder, задачей которого является обработка данных полученных с запроса (data type conversion, data validation и т.д.), а начинают работать фильтры ASP.NET MVC. Сначала выполняется фильтр аутентификации (Authentication filters), а затем фильтр авторизации (Authorizations filters). Action фильтры срабатывают перед началом действия (OnActionExecution) и после его выполнения (OnActionExecuted). Затем роль принимают фильтры, задача которых – возвращать результат Result фильтры. Они также представлены двумя методами, один из которых вызывается до начала возврата результата (OnResultExecution), а второй – после (OnResultExecuted). Детальнее по фильтрам исключений можно познакомиться здесь: Action Filtering in ASP.NET MVC Applications
Теперь по поводу самого результата, который может возвращаться конечному пользователю.  Его условно можно разделить на две части: ту, которая рендерит представление (интерфейс IView), и вторая, для которой ничего генерировать не нужно. Все типы доступных возвращаемых, как ActionResult, вы можете найти здесь, а наиболее часто используемые перечислим: ViewResult, PartialViewResult, RedirectResult, RedirectToRouteResult, ContentResult, JsonResult, FilePathResult, FileContentResult и FileStreamResult.
View initialization and rendering
Рассмотрим отдельно обработку и рендеринг представления, потому что это отличается от остального возвращаемого результата. Тип ViewResult и PartialViewResult представляют собой реализацию интерфейса IView и рендерятся соответствующим движком для представлений.
Отображается все наше представление благодаря движку представления, то есть классу, который наследуется от интерфейса IViewEngine.  Например, стандартным движком по умолчанию есть WebFormViewEngine, шаблоны которого являются страницами WebForm (.aspx). Вы можете для примера определить свой собственный движок и использовать его. Пример такого движка вы можете найти по ссылке Creating a custom View Engine for ASP.NET MVC leveraging Text Template (T4) engine for rendering the view. Использовать это дело довольно просто. Для этого нужно открыть файл Global.asax, затем в методе Application_Start добавить следующий код:
protected void Application_Start()
{
    //Remove All View Engine including Webform and Razor
    ViewEngines.Engines.Clear();
    //Register Your Custom View Engine
    ViewEngines.Engines.Add(new CustomViewEngine());
    //Other code is removed for clarity
Здесь первым делом вы чистите старые движки, и вторым шагом добавляете свои.
Ну и напоследок, активизируются разные Razor хелперы, которые вы неоднократно видели, наверное, как, например @Html.LabelFor и т.д. Это актуально не только для Razor движка, просто я его упомянул в качестве примера.

Итоги
В завершение хотелось бы добавить схему, как это все работает.

Схема взята со статьи Detailed ASP.NET MVC Pipeline. На данной схеме не хватает некоторых мелких деталей, но в целом, она достойно демонстрирует принцип работы ASP.NET. Надеюсь, что статья получилась не очень сухой. Буду рад ответить на ваши вопросы.Удачи вам на пути профессионала ASP.NET MVC!