Saturday, January 30, 2016

Обзор блога за 2015 год

Сегодня поделюсь обзором написанного в блоге за 2015 год и профессиональными планами на 2016-й.
Год 2015 был достаточно насыщенным в плане личного развития и знакомства с новыми технологиями.  За это время я сумел написать 43 статьи, что, к сожалению, намного меньше, чем в 2014 году. Основная причина заключается в том, что больше времени в 2015-м я уделял изучению новых технологий, фреймворков и паттернов. Все-же кое-что за год удалось усвоить.
  1. Достаточно плотно получилось поработать с Caliburn.Micro, что дало новые ощущения и позволило по-другому взглянуть на мир разработки XAML-приложений, используя MVVM фреймворки, не только такие как Prism и MVVM Light.
  2. Разобрался с C# 6, пробуя его в работе, начиная с CTP версии Visual Studio 2015 (которая имела название Visual Studio 14).
  3. Разобрался, как работают движки для высокоскоростного полнотекстового поиска на базе Lucene.Net (Жаль только, что так и не получилось его внедрить в большой проект через более приоритетные задачи).
  4. На конец года я сменил свое место работы и перешел на веб-разработку, что позволило познакомиться с AngularJS, jQuery templates, ASP.NET MVC. Благодаря этому я начал углубляться в ASP.NET vNext, EntityFramework 7 и Angular 2.
  5. Ну и последнее, что получилось сделать в 2015 году, – это посмотреть на изменения Prism 6, а также на такие кроссплатформенные фреймворки для работы с XAML, как Perspex и OmniXAML.
Самой популярной за 2015 год является статья Prism vs Caliburn.Micro vs Catel vs MvvmLight – по обзору самых популярных MVVM фреймворков.
Также я смог попробовать себя в роли преподавателя, прочитав две лекции: Design Patterns; Repository and Unit of Work, материалы по которым можно взять здесь; и вторая лекция была на тему EF Code First features & Web API, в которой я больше раскрыл возможности в EF6 и новинки в EF7, который на момент лекции был уже RC (release candidate). Ссылки на вторую презентацию и примеры можно взять здесь.
Планы на 2016 год
Итак, чего же ожидать увидеть в моем блоге. Напишу основные профессиональные цели на год в виде списка, чтобы в итоге самому с ними свериться.
  • Разобраться с OAuth и OWIN Forms аутентификацией.
  • Выучить глубоко JavaScript и TypeScript
  • ASP.NET vNext with Angular 2.0
  • Попробовать пройти сертификацию Developing ASP.NET MVC 4 Web Applications или даже всю цепочку MCSD: Web Applications.
  • C# 7
  • EntityFramework 7
  • DDD (Experimenting with CQRS and event driven design)
  • Mentoring
  • Read 20 books in English
Думаю, этих планов больше чем достаточно для одного года. Тем более, бывают случаи, когда одолевает ужасная лень, которую тяжело побороть. Так что подписывайтесь на мой блог; надеюсь, вы также найдете для себя что-то позитивное и новое в этом году. А у меня будет дополнительная мотивация, если буду знать, что мои мысли как программиста могут быть кому-то полезными. 

Tuesday, January 19, 2016

О книге “Programming ASP.NET MVC 4: Developing Real-World Web Applications with ASP.NET MVC”

Здравствуйте, уважаемые друзья. Сегодня проведем небольшой обзор книги Programming ASP.NET MVC 4: Developing Real-World Web Applications with ASP.NET MVC, которая на Amazon имеет рейтинг 3.5.
Основная идея, почему я решил написать этот отзыв, – это попытка уговорить вас не тратить от 24 до 40 долларов за эту книгу. Она не стоит запрашиваемых денег. Буквально пару дней назад закончил ее читать, и пока в голове свежие воспоминания и эмоции, которые возникли от этой книги, решил ими с вами поделиться.
Задумкой автора было ознакомить читателя с разработкой ASP.NET MVC 4 приложения на примере сайта интернет-аукциона, который называется Ebuy. Так вот, если вы ожидаете, что в книге будет рассматриваться разработка этого сайта, то вы ошибаетесь. Точнее, ей посвящены только первые несколько, если быть точнее, то пять первых глав. Затем начинаются нереальные перепрыгивания с одной главы на другую. Мне очень понравился небольшой отрывок комментария, взятый с Amazon.
The book tries to walk you through the building of an online auction site, called Ebuy, but indeed it wasn't executed too well. I wasn't planning on writing the code while reading through the book anyway, so personally I didn't care. If you do enjoy coding as you read, however, you will no doubt be disappointed. I checked out the source code for the book on GitHub, and the source code doesn't do the book justice. The sample code needs to be a bit more feature-rich and show off much more of the book's content to be of real use. Hopefully the authors will add more to the source code. That being said, there are still plenty of good and short samples on ASP.NET MVC and related technology in the book that have nothing to do with Ebuy.
Некоторые читатели этой книги написали, что она очень хороша для опытных разработчиков, но будет сложна в понимании для начинающих. Здесь у меня, к сожалению. мнение не совпадает с большинством.
Во-первых, автор пытается в своей книге охватить буквально все: лучшие практики разработки, использование пакетов NuGet; рассказывает, как это тестировать с помощью unit-тестов; как работают движки для генерации представлений на клиенте, как работает deployment с помощью TFS. Читаешь это все – вроде все отлично: много красивых слов дают уверенность, что это точно можно будет как-то применить. Вот тут то вас и ждет наибольшее разочарование. Я, например, прочитал главу по тестированию и был шокирован. Мне очень сложно представить, чем руководствовался автор, когда пример, приведенный для теста, был реализован с жестким прокидываением DbContext, в реализации это была передача EbuyDataContext в конструктор DataContextRepository. Я слегка изменил пример, чтобы ко мне не придрались из-за авторских прав, а оставил идею и реализацию.
public class AuctionsController : Controller
{
    public AuctionsController()
        : this(new DataContextRepository(new EbuyDataContext()))
    {

    }

    public AuctionsController(IRepository repository)
    {

    }
    // GET: Auctions
    public ActionResult Index()
    {
        return View();
    }
}
Посмотрите внимательно на то, как реализован конструктор по умолчанию. А теперь вопрос для читателей. Как тестировал с помощью unit-тестов использование данного контроллера автор книги? Думаю, вы догадались, что тестировался только конструктор со входящим параметром IRepository. Автору стоило бы почитать или описать, как это можно сделать через какой-либо IoC контейнер, а не писать такую реализацию.А заодно немного почитать о том, как подобное тестировать, например, в книге,  The Art of Unit Testing, тем более, автору это бы точно не помешало.
Хотя мне не по душе, когда ты читаешь книгу, видишь в ней явную ошибку и понимаешь, что пример не будет работать 100%. Затем смотришь в исходники и понимаешь, что код отличается. Мало того, что код отличается, так примеров кода очень во всей книге приведено мало, а если и приведены, то вырваны из контекста. Часть из того, что описал автор, не используется в самих исходных кодах по интернет-аукциону.
Мне также не понятно, зачем автор упомянул об использовании NuGet пакетов, учитывая тот факт, что пример, который он привел, не является рабочим. Отдельное слово хотелось бы сказать о развертывании сайтов, о которых рассказывается в книге. У меня вопрос больше к читателям. Как часто вам приходится развертывать сайты на одной и той же машине? Наверняка ответом будет "очень редко". Зато теперь вы сможете узнать, как это делать, прочитав целую главу книги.
Однако в книге есть и позитивные моменты. Некоторые главы вполне достойно расписаны и дают неплохое и компактное представление о том или ином аспекте. Например, мне лично понравилась глава о маршрутизации. 
Итак, прорезюмируем преимущества и недочеты данной книги.
Плюсы:
  • Много теоретической базы
  • Компактная подача материала.

Минусы:
  • Много ошибок в коде
  • Некоторые главы написаны так, что видно, что автор сам не разбирается в них. Например, некоторые главы с описанием архитектуры веб-приложений
  • Сложная для понимания. Часть глав написана с перескакиванием из главы в главу. Такой текст неудобно читать, а еще сложнее понять, что автор хотел этим сказать
  • Не достаточно полно расписаны некоторые примеры

Итоги

Соглашусь, пожалуй, с оценкой в 3 или 3.5 для данной книги. Эта книга уникальна в своем роде, потому что подобных я не встречал. Много теории, которая изложена лаконично, также придают свою изюминку книги. Но от книги с таким названием я ожидал увидеть совсем другое. Как минимум – больше примеров и пояснений, комментариев. Да и хотелось бы видеть меньше ошибок в примерах. Хотя, признаться честно, их все равно меньше, чем в книге MCSD Certification Toolkit (Exam 70-483): Programming in C#, которую мне довелось прочитать и на которую я дал отдельную рецензию. Как минимум, я считаю, что книга не стоит той суммы, которую за нее запрашивают, так как информации, которую вы найдете в книге, вам будет точно не достаточно, чтобы писать Real-World Web Applications

Tuesday, January 12, 2016

Using Web API with jQuery templates

Здравствуйте, уважаемые читатели. Сегодня мы с вами рассмотрим, как можно писать приложения. используя Web API. Я решил разделить свое обозрение на две отдельные статьи: в первой я постараюсь рассказать о том, как использовать Web API и всю клиентскую логику генерировать на клиенте, например, с помощью библиотеки mustache.js; во второй статье будет показано, как можно использовать Web API более элегантно с Angular JS
Приступим к нашей реализации, внося небольшие комментарии по ходу работы. Будем все мы писать на ASP.NET MVC 5, так как шестой – хоть и релиз-кандидат, но использовать его пока рано. Начнем с создания нового проекта, который назовем “StudentWebApiSample”, задача которого  простая регистрация студентов. Никакой сложной структуры мы реализовывать не будем. Вокруг которой таблицы и будем строить всю нашу логику. Напишем все сразу, чтобы можно было расширять это все для более крупных проектов.
Затем выберем шаблон Web API, как показано на рисунке ниже.
Постараемся все рассмотреть быстро, чтобы по минимуму уделять времени на ненужные детали. После того, как ваш проект будет создан, вы можете посмотреть, как выглядит контроллер ApiController, если вам еще не приходилось до этого работать с Web API. По умолчанию создается контроллер ValuesController, который представляет собой тестовую реализацию. Вы можете с ней ознакомиться, по желанию, и сразу удалить. Как минимум, для нашего приложения она не понадобится.
Следующим делом нам нужно создать класс Student, который будет основной лошадкой нашего приложения. Этот класс нам нужно добавить в папку Models. Ниже представлена реализация данного класса.
public class Student
{
    [Key]
    public int Id { get; set; }

    [RegularExpression(@"^[A-Z]+[a-zA-Z''-'\s]*$")]
    [StringLength(50, MinimumLength = 1)]
    [Display(Name = "Last Name")]
    public string LastName { get; set; }

    [Display(Name = "First Name")]
    [StringLength(50, MinimumLength = 2, ErrorMessage = "First name must be between 2 and 50 characters.")]
    public string FirstName { get; set; }

    public string FullName
    {
        get
        {
            return FirstName + " " + LastName;
        }
    }
}
Теперь создадим DbContext для работы с этим классом. Для этого добавим новую паку, которую назовем DAL (Data Access Layer), и в нее добавим новый класс StudentContext.
public class StudentContext : DbContext
{
    public StudentContext() : base("StudentsConnection")
    {
        Database.SetInitializer(new CreateDatabaseIfNotExists<StudentContext>());
    }
    public DbSet<Student> Students { get; set; }
}
По умолчанию Entity Framework установлен с тем темплейтом, который мы выбрали, поэтому все, что я написал выше, у вас должно работать без необходимости подгружать что-либо. Осталось только, чтобы это все заработало, добавить новое подключение в Web.config. Для этого открываем Web.config, ищем в нем connectionStrings и добавляем туда следующую строку подключения:
<add name="StudentsConnection" connectionString="Data Source=.;Initial Catalog=Students;Integrated Security=true" providerName="System.Data.SqlClient" />
Полностью connectionStrings у меня выглядит, как показано ниже.
<connectionStrings>
  <add name="DefaultConnection" connectionString="Data Source=(LocalDb)\MSSQLLocalDB;AttachDbFilename=|DataDirectory|\aspnet-StudentWebApiSample-20151222060005.mdf;Initial Catalog=aspnet-StudentWebApiSample-20151222060005;Integrated Security=True" providerName="System.Data.SqlClient" />
  <add name="StudentsConnection" connectionString="Data Source=.;Initial Catalog=Students;Integrated Security=true" providerName="System.Data.SqlClient" />
</connectionStrings>
После того как мы проделали все действия выше, можно добавить новый Web API контроллер, который сгенерирует нам всю необходимую логику. Для этого выберем папку Controllers, кликнем правой кнопкой мыши и выберем Add-> Controller…, как показано ниже.
Затем в списке выбираем Web API 2 Controller with actions, using Entity Framework
Затем выберем нашу модель, для которой мы хотим сгенерировать контроллер, а также контекст, в котором эта модель присутствует.
Если хотите, можете выбрать для себя галочку использования асинхронного контроллера, тогда ваш котроллер будет построен на тасках (TPL). Наш код будет работать как с асинхронным контролером, так и на синхронном. Поэтому для теста я эту галочку не ставил. (По причине того, что в реальных примерах, с которыми я сталкивался в основном уклон был на синхронные контроллеры).
Ниже представлен контроллер, который был сгенерирован студией.
public class StudentsController : ApiController
{
    private StudentContext db = new StudentContext();

    // GET: api/Students
    public IQueryable<Student> GetStudents()
    {
        return db.Students;
    }

    // GET: api/Students/5
    [ResponseType(typeof(Student))]
    public IHttpActionResult GetStudent(int id)
    {
        Student student = db.Students.Find(id);
        if (student == null)
        {
            return NotFound();
        }

        return Ok(student);
    }

    // PUT: api/Students/5
    [ResponseType(typeof(void))]
    public IHttpActionResult PutStudent(int id, Student student)
    {
        if (!ModelState.IsValid)
        {
            return BadRequest(ModelState);
        }

        if (id != student.Id)
        {
            return BadRequest();
        }

        db.Entry(student).State = EntityState.Modified;

        try
        {
            db.SaveChanges();
        }
        catch (DbUpdateConcurrencyException)
        {
            if (!StudentExists(id))
            {
                return NotFound();
            }
            else
            {
                throw;
            }
        }

        return StatusCode(HttpStatusCode.NoContent);
    }

    // POST: api/Students
    [ResponseType(typeof(Student))]
    public IHttpActionResult PostStudent(Student student)
    {
        if (!ModelState.IsValid)
        {
            return BadRequest(ModelState);
        }

        db.Students.Add(student);
        db.SaveChanges();

        return CreatedAtRoute("DefaultApi", new { id = student.Id }, student);
    }

    // DELETE: api/Students/5
    [ResponseType(typeof(Student))]
    public IHttpActionResult DeleteStudent(int id)
    {
        Student student = db.Students.Find(id);
        if (student == null)
        {
            return NotFound();
        }

        db.Students.Remove(student);
        db.SaveChanges();

        return Ok(student);
    }

    protected override void Dispose(bool disposing)
    {
        if (disposing)
        {
            db.Dispose();
        }
        base.Dispose(disposing);
    }

    private bool StudentExists(int id)
    {
        return db.Students.Count(e => e.Id == id) > 0;
    }
}
Как видите, не написав ни строчки кода для контролера, мы уже имеем вполне рабочий проект. Зачастую у вас возможны разные прослойки, например, кастомный UnitOfWork поверх DbContext. Даже в таком случае Scaffolding здорово упростит вам жизнь. потому что создаст вам скелет, который вы сможете адаптировать под свои нужды.
Теперь пришла пора реализовать логику, необходимую для отображения студентов и добавления новых. Чтобы не писать жуткую логику добавления студентов в таблицу, подобно той, которая приведена ниже,
$("#studentTable").append("<tr><td>" + value.Id + "</td><td>" + value.FirstName + "</td><td>" +
    value.LastName + "</td><td>" + "<a>Edit</a>" + "</td><td>" + "<a>Delete</a>" + "</td></tr>");
нам понадобится библиотека для работы с темплейтами, которая позволит нам создавать наш HTML код, и заполнять его сразу реальными данными. Для этого я воспользовался библиотекой mustache.js, которую можно установить через NuGet Package Manager.
Если вам нужна более свежая версия, то вы можете поставить ее через npm. как описано об этом на github.com mustache.js.
Нам же достаточно того, что есть в поставке. Для более сложных вещей, конечно же, вам будет mustache.js маловато, но есть очень большое количество альтернатив. Одну из таких альтернатив я описал в статье "Generate client views with jQuery templates in ASP.NET MVC. RsRender Engine". В этой же статье есть перечень популярных альтернатив, которые вы можете использовать.
После того как мы поставили через NuGet Package Manager себе mustache, нужно добавить загрузку его в наше приложение. Для этого перейдем в класс BundleConfig и в методе RegisterBundles добавим в самый конец следующий код:
bundles.Add(new ScriptBundle("~/bundles/custom").Include(
                    "~/Scripts/mustache.js"));
Затем нам нужно добавить загрузки только что созданного бандла в нашу страницу. Для этого ищем в папке Shared файл _Layout.cshtml, в котором в самом конце добавлены следующие строки:
@Scripts.Render("~/bundles/jquery")
@Scripts.Render("~/bundles/bootstrap")
Сразу после них добавляем загрузку нашего бандла.
@Scripts.Render("~/bundles/custom")
Теперь пришло время правки нашей главной страницы. Для этого откроем нашу страницу Index.cshtml и все удалим с нее. Затем добавим простую таблицу, как показано ниже.
<div id="mainWindow">
    <div class="row">
        <button id="addNewStudent" class="btn btn-primary">Create New</button>
    </div>
    <div class="row">
        <table id="studentTable" class="table table-bordered">
            <thead>
            <tr>
                <th>ID</th>
                <th>
                    First Name
                </th>
                <th>
                    Last Name
                </th>
                <th style="width: 40px">
                </th>
                <th style="width: 40px">
                </th>
            </tr>
            </thead>
            <tbody></tbody>
        </table>
    </div>
</div>
И следующий JavaScript код, который будет представлять собой шаблон для нашей таблицы.
<script id="studentRowTemplate" type="text/html">
    <tr>
        <td>
            {{ Id}}
        </td>
        <td>
            {{ FirstName }}
        </td>
        <td>
            {{ LastName }}
        </td>
        <td>
            <button class="btn btn-danger btn-xs btn-block" style="width: 36px" id="editButton" onclick="editRow({{Id}}) "><span class="glyphicon glyphicon-edit"></span></button>
        </td>
        <td>
            <button class="btn btn-danger btn-xs btn-block" style="width: 36px" id="removeButton" onclick="removeRow({{Id}}) "><span class="glyphicon glyphicon-trash"></span></button>
        </td>
    </tr>
</script>
Студия, правда, может ругаться на такую реализацию методов, но у вас реально будет сложность с подпиской на события через jQuery, потому что на момент создания страницы у вас не будет создано контролов. То есть, следующий код работать не будет.
<script src="~/Scripts/jquery-1.10.2.js"></script>
<script type="text/javascript">

    $(function () {

        $("#removeButton").on('click', function (e) {
            e.preventDefault();
            console.log("removeButton click");      
        });
    });
</script>
Основная причина – это то, что мы используем темплейты, которые создают наши элементы динамически. Поэтому здесь будет работать event-delegation. Описание самой проблемы вы можете найти здесь jQuery .click() won't fire from an <a> within a mustache.js template.
К реализации этих обработчиков мы перейдем немного позже. Нам нужен код, который загрузит наших студентов в таблицу. Для этого перепишем наш код выше следующим образом:
<script src="~/Scripts/jquery-1.10.2.js"></script>
<script type="text/javascript">

    $(function () {

        $.getJSON(
             "api/Students",
             function (data) {
                 var template = $('#studentRowTemplate').html();

                 $.each(data,
                     function (index, value) {
                         var info = Mustache.render(template, value);
                         $('#studentTable >tbody').append(info);
                     }
                 );
             }
         );
    });
</script>
Запустим наше приложение и убедимся в том, что метод api/GetStudents успешно вызывается.
Наверное, было бы лучше для тестового примера сделать начальные данные для запуска, чтобы убедиться, что начальная часть у нас работает. Для этого перейдем в папку App_Start и добавим новый класс SampleData, как показано ниже.
using System.Linq;
using StudentWebApiSample.DAL;
using StudentWebApiSample.Models;

namespace StudentWebApiSample
{
    public static class SampleData
    {
        public static void Initialize()
        {
            using (var context = new StudentContext())
            {
                if (!context.Students.Any())
                {
                    context.Students.Add(
                        new Student {LastName = "Austen", FirstName = "Jane"});
                    context.Students.Add(
                        new Student { LastName = "Dickens", FirstName = "Charles"});
                    context.Students.Add(
                        new Student { LastName = "Cervantes", FirstName = "Miguel"});

                    context.SaveChanges();
                }
            }
        }
    }
}
После того, как мы добавили наш класс, открываем файл Global.asax и вконце добавим следующую строку кода:
SampleData.Initialize();
Полный код выглядит следующим образом:
public class WebApiApplication : System.Web.HttpApplication
{
    protected void Application_Start()
    {
        AreaRegistration.RegisterAllAreas();
        GlobalConfiguration.Configure(WebApiConfig.Register);
        FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
        RouteConfig.RegisterRoutes(RouteTable.Routes);
        BundleConfig.RegisterBundles(BundleTable.Bundles);
        SampleData.Initialize();
    }
}
После того, как мы внесли все необходимые данные, запустим наш сайт и проверим, что у нас все работает.
После того, как мы добавили отображение наших студентов (которые являются авторами популярных книг), следующим делом добавим удаление студентов по нажатию на кнопкуУ нас уже добавлен обработчик на эту кнопку. Осталось только ее реализовать. Выглядеть эта функция будет следующим образом:
function removeRow(id) {
    $.ajax({
        url: "api/Students/" + id,
        type: 'DELETE',
        success: function () {
            window.location.href = "";
        },
        fail: function (data) {
            console.log(data);
        }
    });
}
Полный код использования приведен ниже.
<script type="text/javascript">

    function removeRow(id) {
        $.ajax({
            url: "api/Students/" + id,
            type: 'DELETE',
            success: function () {
                window.location.href = "";
            },
            fail: function (data) {
                console.log(data);
            }
        });
    }

    $(function () {

        $.getJSON(
             "api/Students",
             function (data) {
                 var template = $('#studentRowTemplate').html();

                 $.each(data,
                     function (index, value) {
                         var info = Mustache.render(template, value);
                         $('#studentTable >tbody').append(info);
                     }
                 );
             }
         );
    });
</script>
Теперь приступим к созданию окна, которое будет у нас служить для добавления новых студентов и редактирования старых. Для этого в наш html код добавим следующие строки:
<div id="createNewStudent">
    <div class="row">
        <form class="form-horizontal" role="form" name="studentForm">
            <div id="studentInformation">

            </div>

            <div class="form-group">
                <div class="col-md-offset-2 col-md-10">
                    <button class="btn btn-primary" id="saveStudent">Save</button>
                    <button class="btn btn-primary" id="cancelStudent">Cancel</button>
                </div>
            </div>
        </form>
    </div>
</div>

<script id="editStudentTemplate" type="text/html">
    <input type="hidden" id="studentId" value="{{Id}}">
    <div class="form-group">
        <label class="control-label col-md-2" for="firstName">First Name:</label>
        <div class="col-md-10">
            <input id="firstName" class="form-control" value="{{FirstName}}" />
        </div>
    </div>

    <div class="form-group">
        <label class="control-label col-md-2" for="lastName">Last Name:</label>
        <div class="col-md-10">
            <input id="lastName" class="form-control" value="{{LastName}}" />
        </div>
    </div>
</script>
Здесь мы создали новый div для отображения окна по созданию и редактированию новых студентов. А ниже добавлен темлейт, который отображает позиционирование контролов и binding их с данными. Если мы сейчас запустим наш пример, то увидим два окна. С таблицей для отображения и для добавления/редактирования. Это не то, чего мы хотим добиться.
Поэтом в наш JavaScript код, в котором у нас реализована вся логика, добавим следующие строки:
$("#mainWindow").show();
$("#createNewStudent").hide();
Теперь необходимо добавить реализацию на кнопку Create New для регистрации новых студентов.
$(function() {
    $("#addNewStudent").on('click', function () {
        console.log("addNewStudent click");
        var studentTemplate = $('#editStudentTemplate').html();
        var info = Mustache.render(studentTemplate, null);

        $("#mainWindow").hide();
        $("#studentInformation").html(info);
        $("#createNewStudent").show();
    });
});
Здесь мы добавили подписку на кнопку addNewStudent через Id, а также сгенерировали новый темлпейт для добавления информации по студентам. Теперь у нас по нажатию на кнопку будет появляться другая форма, которая будет закрываться по нажатию на кнопку Save или Cancel.
Теперь давайте реализуем обработчик для кнопки Save. Ниже представлена реализация данного обработчика, которую я сделал как для редактирования, так и для добавления новых данных в базу данных.
$("#saveStudent").on('click', function (e) {
    e.preventDefault();
    var student = new Object();
    student.firstName = $('#firstName').val();
    student.lastName = $('#lastName').val();
    var id = parseInt($('#studentId').val());

    //insert or update
    if (isNaN(id)) {
        $.post('api/Students', student, function (data) {
            console.log(data);
            window.location.href = "";
        });
    }
    else {
        student.id = id;
        $.ajax({
            url: 'api/Students/' + student.id,
            type: 'PUT',
            data: student,
            success: function (response) {
                window.location.href = "";
            }
        });
    }
});
Здесь все просто. Создаем объект на JavaScript, который будет мапиться на наш класс Student. Запустим и посмотрим, что у нас добавляются новые значения в таблицу. Попробуем добавить нового студента по имени John Carter.
Проверяем, что новый студент успешно добавлен.
Теперь осталось добавить редактирование для текущих студентов, иначе наше приложение будет неполным. Для этого или после метода removeRow, или перед ним нужно добавить следующий метод:
function editRow(id) {
    $.getJSON("api/Students/" + id, function (data) {
        var studentTemplate = $('#editStudentTemplate').html();
        var info = Mustache.render(studentTemplate, data);
        $("#mainWindow").hide();
        $("#studentInformation").html(info);
        $("#createNewStudent").show();
    });
}
Здесь мы выбираем студента с нашего сервера по id, а затем отправляем его данный в сгенерированный через mustache темплейт. Перезапустим наш проект и поменяем нашего John Carter на John Lennon. Теперь кнопка редактирования подгрузит нам всю информацию о John Carter.
Нам останется ее лишь немного изменить.
Проверим, что у нас все обновилось.
Ниже для проверки приведу весь исходный код.
<div id="mainWindow">
    <div class="row">
        <button id="addNewStudent" class="btn btn-primary">Create New</button>
    </div>
    <div class="row">
        <table id="studentTable" class="table table-bordered">
            <thead>
                <tr>
                    <th>ID</th>
                    <th>
                        First Name
                    </th>
                    <th>
                        Last Name
                    </th>
                    <th style="width: 40px">
                    </th>
                    <th style="width: 40px">
                    </th>
                </tr>
            </thead>
            <tbody>

            </tbody>
        </table>
    </div>
</div>

<div id="createNewStudent">
    <div class="row">
        <form class="form-horizontal" role="form" name="studentForm">
            <div id="studentInformation">

            </div>

            <div class="form-group">
                <div class="col-md-offset-2 col-md-10">
                    <button class="btn btn-primary" id="saveStudent">Save</button>
                    <button class="btn btn-primary" id="cancelStudent">Cancel</button>
                </div>
            </div>
        </form>
    </div>
</div>

<script id="studentRowTemplate" type="text/html">
    <tr>
        <td>
            {{ Id}}
        </td>
        <td>
            {{ FirstName }}
        </td>
        <td>
            {{ LastName }}
        </td>
        <td>
            <button class="btn btn-danger btn-xs btn-block" style="width: 36px" onclick="editRow({{Id}})"><span class="glyphicon glyphicon-edit" ></span></button>
        </td>
        <td>
            <button class="btn btn-danger btn-xs btn-block" style="width: 36px" onclick="removeRow({{Id}})"><span class="glyphicon glyphicon-trash"></span></button>
        </td>
    </tr>
</script>

<script id="editStudentTemplate" type="text/html">
    <input type="hidden" id="studentId" value="{{Id}}">
    <div class="form-group">
        <label class="control-label col-md-2" for="firstName">First Name:</label>
        <div class="col-md-10">
            <input id="firstName" class="form-control" value="{{FirstName}}" />
        </div>
    </div>

    <div class="form-group">
        <label class="control-label col-md-2" for="lastName">Last Name:</label>
        <div class="col-md-10">
            <input id="lastName" class="form-control" value="{{LastName}}" />
        </div>
    </div>
</script>

<script src="~/Scripts/jquery-1.10.2.js"></script>
<script type="text/javascript">
    $("#mainWindow").show();
    $("#createNewStudent").hide();

    function editRow(id) {      

        $.getJSON("api/Students/" + id, function (data) {
            var studentTemplate = $('#editStudentTemplate').html();
            var info = Mustache.render(studentTemplate, data);
            $("#mainWindow").hide();
            $("#studentInformation").html(info);
            $("#createNewStudent").show();
        });
    }

    function removeRow(id) {
        $.ajax({
            url: "api/Students/" + id,
            type: 'DELETE',
            success: function (result) {
                window.location.href = "";
            },
            fail: function (data) {
                console.log(data);
            }
        });
    }

    $(function () {
        debugger;
        $.getJSON(
            "api/Students",
            function (data) {
                var template = $('#studentRowTemplate').html();

                $.each(data,
                    function (index, value) {
                        var info = Mustache.render(template, value);
                        $('#studentTable >tbody').append(info);
                        //$("#studentTable").append("<tr><td>" + value.Id + "</td><td>" + value.FirstName + "</td><td>" +
                        //    value.LastName + "</td><td>" + "<a>Edit</a>" + "</td><td>" + "<a>Delete</a>" + "</td></tr>");
                    }
                );
            }
        );

        $("#saveStudent").on('click', function (e) {
            e.preventDefault();
            var student = new Object();
            student.firstName = $('#firstName').val();
            student.lastName = $('#lastName').val();
            var id = parseInt($('#studentId').val());

            //insert or update
            if (isNaN(id)) {
                $.post('api/Students', student, function (data) {
                    console.log(data);
                    window.location.href = "";
                });
            }
            else {
                student.id = id;
                $.ajax({
                    url: 'api/Students/' + student.id,
                    type: 'PUT',
                    data: student,
                    success: function (response) {
                        window.location.href = "";
                    }
                });
            }
        });

        $("#cancelStudent").on('click', function () {
            window.location.href = "";
        });

        $("#addNewStudent").on('click', function () {
            console.log("addNewStudent click");
            var studentTemplate = $('#editStudentTemplate').html();
            var info = Mustache.render(studentTemplate, null);

            $("#mainWindow").hide();
            $("#studentInformation").html(info);
            $("#createNewStudent").show();
        });
    });
</script>

Итоги
Давайте подведем итоги того, что же у нас получилось. Мы с вами рассмотрели, как использовать Web API на клиенте. Как минимум, вы поняли, что использовать Web API на стороне клиенте с генерацией темплйтов – задача не из простых. Разные темплейт генераторы, такие как mustache или JsRender, не особо спасают ситуацию. На помощь и выручку нужно брать все-таки более высокоуровневые UI фреймворки, как AngularJS или KnockoutJS, потому что как вы выйдите с MVC работать проще, чем с Web API. В следующей статье мы с вами рассмотрим, как использовать этот же подход с AngularJS, чтобы убедиться, что все может быть не настолько страшно.

Исходники: Student Sample