28 января 2010

ASP.NET MVC и jQuery – Dynamic DropDowns

Встречаются ситуации, когда при разработке приложения вам нужно иметь выпадающий список, значения которого зависят от выбранного значения в другом выпадающем списке. Классический пример – форма обратной связи, подгружающей значения штатов и областей на основании выбранной страны. Например, если я выберу «United States», то во втором списке я хочу получить соответсвтующие штаты: New York, Alabama, Texas и т.д. Если же я выберу «Canada» - области Nova Scotia и Ontario. К счасть с использованием ASP.NET MVC и jQuery достичь такой функциональности довольно просто. А главное мы сможем это сделать без постбэков, используя jQuery AJAX.

Для начала убедитесь, что вы ссылаетесь на библиотеку jQuery, желательно в теге HEAD. Для этого примера взята верся jQuery 1.4. Вы можете скачать ее локально, либо использовать сервис Google:

<script src="/Scripts/jquery-1.4.min.js"type="text/javascript"></script>

или

<scriptsrc="http://ajax.googleapis.com/ajax/libs/jquery/1.4/jquery.min.js" type="text/javascript"></script>

Теперь наш тестовый HTML. Как можно увидеть список стран заранее заполнен и по-умолчанию выбрано пустое значение.

<table>
 <tr>
  <th>Country:</th>
  <td>
   <select id="countries">
    <option> -- Select a Country -- </option>
    <option value="1">United States</option>
    <option value="2">Canada</option>
    </select>
  </td>
 </tr><tr>
  <th>State:</th>
  <td><select id="states"></select></td>
 </tr>
</table>

Это наш контроллер, который будет снабжать нас странами и областями для выбранной страны

 var list = new object[] {};

 switch (countryID) {
  case 1:
   list = new object[] {
    new { value = 1, name = "Alabama" },
    new { value = 2, name = "Alaska" },
    new { value = 3, name = "California" }
   };
   break;
  case 2:
   list = new object[] {
    new { value = 1, name = "Ontario" },
    new { value = 2, name = "Quebec" },
    new { value = 3, name = "Nova Scotia" }
   };
   break;
 };

 return Json(list);
}

 

Значение специально захардкожены для простоты демонстрации. В реально жизни их стоить грузить из, например, базы данных или xml файла. Обратите внимание на сигнатуру метода. Мы принимаем единственный параметр countryID, который определяет какие данные должны быть возвращены. Мы также установили тип возвращаемого значения в JsonResult. Это означает, что объект, возвращаемый из метода будет сериализован как JSON объект, который может быть легко использован из jQuery. Закончив с контроллером перейдем к коду jQuery.

<script type="text/javascript">
 $(document).ready(function() {    
  $("#countries").change(function() {     
   $.post("/Home/GetStates/", { countryID: $(this).val() }, function(data) {
    populateDropdown($("#states"), data);
   });
  });
 });

 function populateDropdown(select, data) {
  select.html('');
  $.each(data, function(id, option) {
   select.append($('<option></option>').val(option.value).html(option.name));
  });       
 }
</script>

 

Разберем его. Сначала мы привязываем событие к выпадающему списку с выбором страны. Когда происходит это событие мы выполняем ассинхронный запрос метода GetStates контроллера Home. Выбранное значение страны передается в метод как переменная: { countryID: $(this).val() }. Когда мы получаем ответ мы передаем полученные данные в функцию populateDropdown, которая очищает выпадающий список штатов и областей и заполняет его новыми значениями посреством добавления элемента option и установки его параметров value и text.

Код проекта можно скачать по ссылке ниже:

DynamicDropDown.zip (37.28 kb)

Перевод статьи Dynamic Select Lists with MVC and jQuery

24 января 2010

Windows 7 32 bit - 4GB of RAM

Всем давно известно, что 32-битная версия ОС не может адресовать 4GB памяти. Даже если их установить в систему будет видно всего-лишь около 3GB. Однако Windows Server 2008 32bit умеет видеть больше, а Windows 7 построена как раз на его ядре. Поддержка данной возможности отключена по лицензионным соображениям и есть метод ее включить.

Внимание! Описанный ниже метод влечет изменение ядра системы и может вызвать непредсказуемые последствия! В силу этих же причин загружаемый файл может определятся антивирусами как вредоносный! Используя этот метод вы полностью возлагаете на себя ответственность за последствия!

Шаг 1
Загрузите файл: narod.ru, rapidshare.com

Шаг 2
Запустив его вы увидите данное окно:

В нем необходимо нажать на кнопку "Разблокировать"

Шаг 3
Через некоторое время появится сообщение об успешном завершении операции. После нажатия "ОК" отроется следующее окно:

В нем нужно нажать клавишу "Y" для выполнения действий и "Enter" после их окончания. После этого компьютер должен перезагрузиться.

Шаг 4
После загрузки системы откройте меню "Пуск", нажмите правой кнопкой на пункте "Компьютер" и выберите "Свойства".
Если все прошло успешно в появившемся окне вы увидите, что теперь используются все 4GB памяти.

В моем случае это выглядит так(256Mb забирает себе видеокарта).


Шаг 5(опциональный)
Чтобы уменьшить задержку автовыбора в меню загрузки ОС проделайте следующие действия: Откройте меню "Пуск", наберите "msconfig" и нажмите "Enter".
В открывшемся окне выберите вкладку "Загрузка" и установите значение поля "Таймаут" в 3 секунды(меньше, к сожалению нельзя).

Шаг 6(опциональный)
Если на рабочем столе в правом углу появилась надпись "Test Mode Windows 7 Build 7600" то выполните следующие действия:
Откройте меню "Пуск", введите "mcbuilder" и нажмите "Enter".
Дождитесь закрытия открывшегося окна и перезагрузите компьютер.

Информация взята с сайта pallab.net

13 ноября 2008

Switcher - Аналог Expose под Windows Vista

В скором будущем, когда позволит финансовая ситуация, я стану свитчером - перейду на Mac OS X. Сейчас же денег нет и я пытаюсь сформировать макоподобный интерфейс в Windows Vista. 

И вроде почти ни с чем проблем не было - кроме так называемого Expose. Но после некоторого времени, проведенного в гугле и отбрасыванием бесполезных/умерших утилит я наткунлся на программу под приятным мне названием - Switcher. 

По функционалу - тот же Expose но еще более настраиваемый. Что не менее отрадно - полностью бесплатно. Позволяет активировать функцию из угла экрана либо по сочетанию клавиш(можно даже оверрайдить Win+Tab). Единственное требование Windows Vista с работающим Aero.

04 ноября 2008

Новые возможности C# 4.0. Часть 3: Ковариантность обобщений

Когда обобщения(generic) пришли к нам вместе с C# 2.0, они стали одной из лучших возможностей в этом языке. Те, кто когда-либо создавал классы строготипизированных коллекций в C# 1.0 знает, насколько они упростили нам жизнь и уменьшили количество кода. Единственная проблема заключалась в том, что обобщенные типы не следовали тем же правилам наследования, которые были в силе для обычных типов. Начнем, пожалуй с определения двух простых классов, которые мы будем использовать в этой статье:

   1:  public class Shape
   2:  {
   3:  }
   4:   
   5:  public class Circle : Shape
   6:  {
   7:  }

Это классическая иерархия классов, которая пока ничего конкретного не делает, но это нам сейчас и не нужно. Теперь определим dummy-класс, который может быть контейнером для любого типа.

   1:  public interface IContainer<T>
   2:  {
   3:      T GetItem();
   4:  }
   5:   
   6:  public class Container<T>: IContainer<T> 
   7:  {
   8:      private T item;
   9:   
  10:      public Container(T item)
  11:      {
  12:          this.item = item;
  13:      }
  14:   
  15:      public T GetItem()
  16:      {
  17:          return item;
  18:      }
  19:  }

У нас есть иерархия и контейнер. Теперь посмотрим на то, что мы сейчас не можем делать в текущей версии C# - 3.0.

   1:  static void Main(string[] args)
   2:  {            
   3:      IContainer<Shape> list = GetList();
   4:  }
   5:   
   6:  public static IContainer<Shape> GetList()
   7:  {
   8:      return new Container<Circle>(new Circle());
   9:  }

У нас есть метод GetList, у которого тип возращаемого значения определен как IContainer<Shape>, а возвращает он Container<Circle>. Так как Circle наследуется от Shape, а Container реализует интерфейс IContainer может показаться, что это сработает. Но, как вы догадались, C# 3.0 на это не способен.

В C# 4.0 у нас есть путь заставить это работать - нам нужно просто добавить ключевое слово out к параметру-типу в нашем определении интерфейса IContainer (замечу, что ковариантность в C# 4.0 ограничена интерфейсами и делегатам).

   1:  public interface IContainer<out T>
   2:  {
   3:      T GetItem();
   4:  }

Данная конструкция говорит компилятору, что тип T ковариантен, что означет то, что любой IContainer<T> будет принимать любой тип эквивалентный или более конкретный, чем T. Как мы видели выше, типом возвращаемого значения был IContainer<Shape>, но если мы поставим параметр out к нашему интерфейсу, то мы легко сможем вернуть и IContainer<Circle>.

Так почему же решено использовать ключевое слово out? Все потому, что когда вы определяете параметр-тип как ковариантный вы сможете только вернуть этот тип из интерфейса. Например такая контрукция недопустима:

   1:  public interface IContainer<out T>
   2:  {
   3:      void SetItem(T item);
   4:      T GetItem();
   5:  }

Но почему это не будет работать? Ответ на самом деле очень прост - безопасность типов. Давайте посмотрим на последствия того, что мы сделали:

   1:  static void Main(string[] args)
   2:  {
   3:      IContainer<Shape> container = new Container<Circle>();
   4:      SetItem(container);
   5:  }
   6:   
   7:  public static void SetItem(IContainer<Shape> container)
   8:  {
   9:      container.SetItem(new Square()); // BOOM!!!
  10:  }

Так как T ковариантен и, поэтому, мы можем присвоить Container<Circle> к переменной типа IContainer<Shape>, передав его дальше в наш статический метод SetItem, который принимает параметр типа IContainer<Shape> и, затем мы берем этот парметр и пытаемся добавить переменную типа Square в него. Кажется, что все верно - тип параметра IContainer<Shape> и это дает нам право добавить в него Square. Неверно. Это выражение "взорвется", так как мы пытаемся добавить Square в контейнер, который держит Circle. Поэтому, ключевым словом out, они ограничили ковариантность только одним направлением.

Вам, наверное, интересно как это все реализовано в CLR 4.0? В реализации нет нужды. Обобщенные типы работали в CLR 2.0 и они уже позволяли подобное поведение. Так как C# пытался сохранить безопасность типов - нам такое не дозволялось, но CLR справляется с этим на раз. И, небольшая ремарка - массивы в C# позволяют такое поведение, так что попробуйте. Я надеюсь, что вам понравилась эта статья и новая в этой серии не за горами!

Перевод статьи C# 4.0 New Features Part 3 - Generic Covariance

03 ноября 2008

Xbox 360 NXE: Второе рождение Xbox 360

19 ноября Microsoft выпустит обновление для интерфейса игровой приставки Xbox 360 - NXE. Это действительно крупное обновление, которое будет включать в себя следующие изменения:

Обновленный интерфейс

Переработана группировка меню - больше не придется искать куда же положился ваш свежескачанный ролик или демоверсия. Кроссылки в интерфейсе, например переходы от описания в новости, непосредственно к покупке.

Визуальное представление также улучшено - интерфейс теперь несколько напоминает CoverFlow из iTunes. И, естественно, он стал более красивым.

Установка игр на жесткий диск

Благодаря этому изменению происходит существенное ускорение загрузки игр. Многие игры, например GTA IV, избавляются от лагов при загрузке текстур в тяжелых местах. Уменьшается шум DVD-привода - чтение идет не с него, а с жесткого диска. Однако диск в приводе держать все же придется - еще одна защита от пиратов.

Аватар пользователя

Это человечек, которого будут видеть ваши друзья в своем в списке людей - идеологически полный аналог аватаров Wii Mii. Полностью кастомизируемый внешний вид - габариты, сложение, лицо, одежда - этакий Sims.

И, напоследок, несколько видео.

Сравнение загрузки GTA IV с жесткого диска и с DVD-диска

Приветствие

Аватары

ASP.NET MVC + jQuery = рай для AJAX

Я никогда раньше не имел дело ни с одним AJAX Toolkit от Microsoft, но недавно у меня появилась необходимость добавить картографический функционал к проекту, который я веду. Нам нужно было, чтобы у пользователей появилась возможность перемещать маркер по карте, а у нас получать его новые координаты на сервере. Очевидно, что нам для этого придется использовать AJAX в каком-либо виде. Сегодня я вам покажу насколько просто использовать связку ASP.NET MVC на сервере и jQuery на клиенте. Так как jQuery теперь включен в поставку ASP.NET MVC, то больше нет оправдания его не использовать.

Возьмем очень простой пример. У нас есть страница на которой я хочу выводить список людей, когда я нажимаю кнопку "Get people" и добавлять нового человека в базу данных, вводя его имя и нажимая "Add person". Вот как это выглядит:

Для начала создадим кнопку "Get people" и список, в который будем загружать людей:

   1:  <input type="button" id="getPeople" value="Get People" />
   2:  <ul id="people_list">

Далее напишем обработчик события "страница загружена", который устанавливает обработчик клика по кнопке.

   1:  $(function() {
   2:      $("#getPeople").click(function() {
   3:          $.getJSON("/Home/People", null, getPeople);
   4:      });
   5:  });

Когда кнопка нажата мы инициализирум запрос JSON данных с пути /Home/People, который мапится на метод People контроллера Home.

   1:  [AcceptVerbs(HttpVerbs.Get)]
   2:  public ActionResult People()
   3:  {
   4:      var db = new DataClasses1DataContext();
   5:      return Json(db.Persons);
   6:  }

Все, что мы тут делаем - это получаем список людей из базы данных(с помощью LINQ to SQL) и возвращаем их в виде JSON. Когда ответ получен браузером вызывается функция getPeople, как у мы указали это в методе getJSON. Вот код функции getPeople:

   1:  function getPeople(people) {
   2:      $("#people_list").text("");
   3:      $.each(people, function(i) {
   4:          $("#people_list").append("<li>" + this.Name + "</li>");
   5:      });
   6:  }

Данные, полученые от сервера приходят в нее в виде параметра people. Все что нам нужно сделать - это итереровать через коллекцию и для каждого ее элемента добавлять тег <li> в наш список людей. Как видите, взаимодействие ASP.NET MVC и jQuery реализуется очень просто - никакой ручной конвертации данных из объектов C# в объекты JavaScript делать не пришлось.

А что насчет обновления данных? Не менее просто. Для начала нам понадобится немного HTML кода: поле ввода и кнопка.

   1:  <label>Name <input type="text" id="name" /></label><br />
   2:  <input type="button" id="addPerson" value="Add Person" />

И еще один обработчик нажатия кнопки.

   1:  $("#addPerson").click(function() {
   2:      var name = $("#name")[0].value;
   3:      if (name == "") {
   4:          alert("You must provide a name");
   5:          return;
   6:      }
   7:      var person = { Name: name };
   8:      $.post("/Home/People", person, null, "json");
   9:  });

На этот раз мы использовали полезную функцию post, чтобы отправить наш свежесозданный объект JSON по адресу /Home/People методом POST. Так как используется метод POST, то этот запрос будет пойман нашим перегруженым методом People с атрибутом AcceptVerbs[HttpVerbs.Post].

   1:  [AcceptVerbs(HttpVerbs.Post)]
   2:  public ActionResult People(string Name)
   3:  {
   4:      var db = new DataClasses1DataContext();
   5:      var person = new Person {Name = Name};
   6:      db.Persons.InsertOnSubmit(person);
   7:      db.SubmitChanges();
   8:      return null;
   9:  }

Все что нам приходится сделать на сервере, чтобы получить данные из запроса это назвать параметр этого метода также, как и поле нашего JSON объекта, то есть Name. ASP.NET MVC автоматически найдет соответсвие и отдаст нам нужные данные. Если вам нужно использовать сложный JSON объект, то вы можете десериализовать его, используя встроеный databinding. Осталось только создать объект для базы данных и сохранить его.

Я поздно занялся Ajax, но со связкой ASP.NET MVC + jQuery добавление такого функционала так же легко и приятно, как дуновение свежего ветра.

Демо-Solution вы можете скачать здесь: http://static.mikehadlow.com/JQueryAjax.zip

Перевод статьи MVC Framework and jQuery = Ajax heaven

02 ноября 2008

Новые возможности C# 4.0. Часть 2: параметры по умолчанию

Сегодня мы поговорим о другой новинке C# 4.0, которую я ждал много лет. В прошлом, ее отсутствие объяснялось архитектурным решением. Но, видимо, прагматизм победил и теперь у нас есть параметры по умолчанию. Чтобы сделать их еще более полезными они добавили к ним именованые параметры. Мы обсудим их через пару минут, а сейчас займемся параметрами по умолчанию.

Предположим, что у нас есть такой класс:

   1:  public class TestClass
   2:  {
   3:      public void PerformOperation(string val1, int val2, double val3)
   4:      {
   5:          Console.WriteLine("{0},{1},{2}", val1, val2, val3);
   6:      }
   7:  }

Теперь мы можем инстанциировать его и вызвать метод:

   1:  var testClass = new TestClass();
   2:  testClass.PerformOperation("val", 10, 12.2);

Но, что, если мы знаем "хорошие" значения параметров, которые используются чаще всего? До сего дня решением являлась перегрузка методов:

   1:  public class TestClass
   2:  {
   3:      public void PerformOperation()
   4:      {
   5:          PerformOperation("val", 10, 12.2);
   6:      }
   7:      
   8:      public void PerformOperation(string val1)
   9:      {
  10:          PerformOperation(val1, 10, 12.2);
  11:      }
  12:   
  13:      public void PerformOperation(string val1, int val2)
  14:      {
  15:          PerformOperation(val1, val2, 12.2);
  16:      }
  17:   
  18:      public void PerformOperation(string val1, int val2, double val3)
  19:      {
  20:          Console.WriteLine("{0},{1},{2}", val1, val2, val3);
  21:      }
  22:  }

Довольно длинно. Но C# 4.0 дает нам лучшее решение:

   1:  public class TestClass
   2:  {
   3:      public void PerformOperation(string val1 = "val", int val2 = 10, double val3 = 12.2)
   4:      {
   5:          Console.WriteLine("{0},{1},{2}", val1, val2, val3);
   6:      }
   7:  }

Насколько это чище, а? Как же нам теперь вызывать этот метод? Да точно так же, если бы это была перегрузка:

   1:  var testClass = new TestClass();
   2:  testClass.PerformOperation("val", 10);

Очень хорошо. Третий параметр в этом вызове приравнен к 12.2 по умолчанию. Теперь VB.NET разработчики перестанут смеятся над нами. Более того, параметры по умолчанию распространяются и на конструкторы:

   1:  public class TestClass
   2:  {
   3:      public TestClass(string someValue = "testValue")
   4:      {
   5:      }
   6:   
   7:      public void PerformOperation(string val1 = "val", int val2 = 10, double val3 = 12.2)
   8:      {
   9:          Console.WriteLine("{0},{1},{2}", val1, val2, val3);
  10:      }
  11:  }

Больше никаких многократных перегрузок конструкторов!

А что случится, если мы опустим значение val2 в вызове метода, показаном выше? Тоесть мы хотим указать val1 и val3, но оставить значение по умолчанию для val2. Мы не можем сделать это так:

   1:  var testClass = new TestClass();
   2:  testClass.PerformOperation("val", 10.2);

Этот код не скомпилируется, так как 10.2 не может быть приведено к int - здесь C# пытается оставить по умолчанию третий параметр, а не второй, как нам нужно. Итак, какой выход у нас есть? Мы можем использовать именованые параметры. Они состоят из указания имени параметра, двоеточия и значения, которое мы передаем. Тоесть вызов будет выглядеть так:

   1:  var testClass = new TestClass();
   2:  testClass.PerformOperation("val", val3: 10.2);

Довольно аккуратно, но меня смущает тот факт, что теперь смена имени параметра будет нести такие кардинальные последствия. Я думаю только время покажет насколько это удобно в разработке больших приложений. Хотя, люди, работающие с другими языками живут с этим уже много лет.

Вот вам еще одна интересная новая возможность C# 4.0 и еще один повод приглядываться к новой VS2010.

Перевод статьи: C# 4.0 New Features Part 2 - default and named parameters

01 ноября 2008

Новые возможности C# 4.0. Часть 1: dynamic

Одна из самых интересных возможностей язык C# 4.0, который был представлен на PDC является новое ключевое слово - dynamic. Оно позволяет разработчику объявить объект, привязка к методам которого, будет осуществлятся на этапе выполнения, а не компиляции. Примечательно, что класс, который инстанциирует этот объект объявляется стандартным способом.

   1:  public class TestClass
   2:  {
   3:      public void TestMethod1()
   4:      {
   5:          Console.WriteLine("test method 1");
   6:      }
   7:   
   8:      public void TestMethod2()
   9:      {
  10:          Console.WriteLine("test method 2");
  11:      }        
  12:  }

Теперь мы можем вызывать его методы как обычно:

   1:  var test = new TestClass();
   2:  test.TestMethod1();
   3:  test.TestMethod2();

Как мы и ожидали все прекрасно скомпилировалось. Теперь воспользуемся новым ключевым словом dynamic:

   1:  dynamic test = new TestClass();
   2:  test.TestMethod1();
   3:  test.TestMethod2();

Ничего не изменилось? Отнюдь. Все точно так же компилируется, как и раньше, но теперь вызовы методов будут привязываться на этапе выполнения, а не компиляции. Тоесть, если мы напишем вот так:

   1:  dynamic test = new TestClass();
   2:  test.TestMethod1();
   3:  test.TestMethod2();
   4:  test.TestMethod3();

Код все-равно успешно скомпилируется, но после запуска мы получим данное исключение:

Что же тут произошло? Вот что нам подсказал Reflector:

   1:  private static void Main(string[] args)
   2:  {
   3:      object test = new TestClass();
   4:      if (<Main>o__SiteContainer0.<>p__Site1 == null)
   5:      {
   6:          <Main>o__SiteContainer0.<>p__Site1 = CallSite<Action<CallSite, object>>.Create(new CSharpCallPayload(Microsoft.CSharp.RuntimeBinder.RuntimeBinder.GetInstance(), false, false, "TestMethod1", typeof(object), null));
   7:      }
   8:      <Main>o__SiteContainer0.<>p__Site1.Target(<Main>o__SiteContainer0.<>p__Site1, test);
   9:      if (<Main>o__SiteContainer0.<>p__Site2 == null)
  10:      {
  11:          <Main>o__SiteContainer0.<>p__Site2 = CallSite<Action<CallSite, object>>.Create(new CSharpCallPayload(Microsoft.CSharp.RuntimeBinder.RuntimeBinder.GetInstance(), false, false, "TestMethod2", typeof(object), null));
  12:      }
  13:      <Main>o__SiteContainer0.<>p__Site2.Target(<Main>o__SiteContainer0.<>p__Site2, test);
  14:  }

Для начала компилятор сгенерировал поле o__SiteContainer0, для хранения информации о месте вызова. Если вы посмотрите на переменную test, которая содержит наш объект, то увидите, что ее тип стал типом object. Так что, в реальности, это не динамический тип, а всего-лишь помощник со стороны компилятора.

Дальше идет проверка мест вызова(callsite) на null и если они ему равны, то используется CallSite.Create, в который передается объект CSharpCallPayload, который содержит всю информацию о методе, который мы вызываем. Как только место вызова указано, то оно просто вызывается у нашей переменной test. Как видите - компилятор все сделал за нас.

В данном примере то что мы делаем кажется чертовски бесполезным, но мощь этой возможности проявляется тогда, когда мы используем тип, методы которого мы не знаем на этапе компиляции. Это может прийти от динамических языков(например IronRuby), если это сгенерированный класс, или что угодно из того, для чего мы используем Reflection.

Ремарка: я принял рещение исключить из перевода часть про тестирование производительности, так как в статье C# 4.0 New Features Part 1.1 - dynamic keyword second look автор провел новое тестирование и оно показало заметно лучшие результаты.

Перевод статьи C# 4.0 New Features Part 1 - dynamic keyword

31 октября 2008

LINQ to SQL: обновление модели из базы данных

Если вы используете LINQ to SQL в паре с его дизайнером вы наверняка в курсе его неприятного ограничения - невозможно обновить модель из базы данных.

Как ни странно с того момента, как я последний раз изучал эту ситуацию - решение появилось:

Huagati DBML/EDMX Tools для Visual Studio 2008 / .net 3.5

Вот в кратце возможности:

  • LINQ to SQL
    • Стандартизация имен классов и членов (удаление префиксов tbl_, int_, str_, приведение регистра символов)
    • Обновление диаграмы дизайнера из базы данных
    • Обновление документации из базы данных
    • Сравнение диаграмы дизайнера и базы данных
  • ADO.NET Entity Framework
    • Стандартизация имен классов и членов (удаление префиксов tbl_, int_, str_, приведение регистра символов)
UPDATE: к сожалению, выяснилось, что данная утилита платная - будем искать лекарство

Unit Testing. Что это и чем это нам грозит?

Благодаря тлетворному влиянию прекрасного разработчика, моего коллеги, Никиты Говорова я начал изучать Best Practices. Первой из этих практик естественно стало модульное тестирование, к тому же в тот момент подоспел очередной preview ASP.NET MVC, а в месте с ним серия скринкастов MVC Storefront, с которыми я рекомендую ознакомиться, даже если вы не связаны с веб-разработкой.

Итак, модульное тестирование. Концепцию удобнее показать на примере.

Предположим, что перед нами стоит простая задача - написать целочисленный калькулятор. Как ни трудно догадаться основой его будет класс Calculator, который мы с вами и будем писать. Для удобства создадим интерфейс ICalculator, который покажет какие методы нам нужны:

public interface ICalculator
{
   int Add(int first, int second);
   int Substract(int first, int second);
   int Multiply(int first, int second);
   int Divide(int first, int second);
}

Далее нам нужно реализовать этот интерфейс в классе Calculator. Мы будем делать это поэтапно для каждого необходимого нам метода.

Этап 1: Написание теста

Да, как это парадоксально не звучит - сначала пишется тест, потом то, что тестируется. Это основное "неудобство" данной практики для новичков, так как очень сложно заставить писать тест еще до того, как написан сам код.

Для написания теста нам нужно добавить в наш Solution новый проект типа Test Project(в данной статье мы будем использовать встроенные тесты Visual Studio 2008).После его создания у нас появится класс UnitTest1 с атрибутом [TestClass], содержащий в себе метод TestMethod1 с атрибутом [TestMethod].

Для начала переименуем метод во что-нибудь более удобоваримое, например в Calculator_Add_MustAddFirstIntToSecondAndReturnTheResult(). Не пугайтесь такого длинного названия - вызывать напрямую этот метод вам нигде не придется, но это добавит ясность к тесту в том случае, когда их у вас в проекте станет несколько сотен. Основная идея - назвать метод так, чтобы по названию стало ясно, что он делает(тестирует).

Небольшая ремарка: Мой коллега Никита Говоров недавно предложил воспользоваться возможностью C# и писать название тестовых методов на русском языке. Это спорное с моей точки зрения решение, так как может вызвать труднонаходимые опечатки из-за переключения раскладки, но если у вас есть сложности с английским языком, то попробуйте - возможно это то, что вам нужно.

Теперь собственно напишем сам тест, который будет тестировать работу метода Add.

[TestMethod]
public void Calculator_Add_MustAddFirstIntToSecondAndReturnTheResult()
{
   Calculator calculator = new Calculator();

   int result = calculator.Add(1, 2);

   Assert.AreEqual(3, result);
}

Как видите - тут все просто. Мы создаем экземпляр класса Calculator, и вызываем у него метод Add, записывая результат. Последняя инструкция - собственно проверка равен ли первый аргумент(тот, результат, который мы ожидаем) второму(результат, который мы получили). Если это не так - метод Assert.AreEqual выбросит исключение. Тест считается пройденным(Passed), если не было выброшено ни одного исключения.

Этап 2: Провалим тест

На этом этапе мы должны убедится, что то что мы собираемся проверять - действительно это проверяет. Так как мы еще не реализовали сам метод Add запуск теста приведет к провалу(Failed), что нам и нужно. Запуск теста осуществляется кликом по телу метода и вызовом команды контекстного меню Run Tests.

Это может показаться излишним, но действительно довольно часто встречаются ситуации, когда тестовый проходит(Passed), хотя никаких условий этому не обеспечено. Например вы можете ошибиться и проверять вместо длины коллекции возвращенных объектов методом Assert.AreNotEqual(0, collection.Length) ее неравность null методом Assert.IsNotNull(collection). При количестве элементов равном нулю ваш тест пройдет, хотя должен быть Failed.

Этап 3: Реализуем метод

На этом этапе с одной стороны все просто, с другой - не очень. Необходимо написать минимальный функционал, благодаря которому тест пройдет. Для примера возьмем метод Divide. Мы уже написали для него тест на предыдущем этапе:

[TestMethod]
public void Calculator_Divide_MustDivideFirstIntBySecondAndReturnTheResult()
{
   Calculator calculator = new Calculator();

   int result = calculator.Divide(4, 2);

   Assert.AreEqual(2, result);
}

В момент реализации самого метода вы можете подумать: "Черт, а ведь на ноль делить нельзя - надо добавить проверку". И будете не правы, так как ваш тест не учитывает этого функционала. Для написания такого функционала нужно написать еще один тест:

[TestMethod]
public void Calculator_Divide_MustCheckSecondArgumentOnZeroAndThrowExceptionIfTrue()
{
   Calculator calculator = new Calculator();

   bool catched = false;

   try
   {
       int result = calculator.Divide(4, 0);
   }
   catch (Exception ex)
   {
       Assert.IsInstanceOfType(ex, typeof (ArgumentException));
       catched = true;
   }
   Assert.IsTrue(catched, "No exception throwed - something wrong");
}

И только после этого реализовывайте соответствующий функционал.

Этап 4: Проходим тест

Последний этап самый простой - убедится что тест прошел.

Заключение

Как видите использование данной практики связано с большим расходом времени(нормальное соотношение строк кода к тестам - 1:2) и очень большой силой самоконтроля - непросто заставить себя идти ровно по шагам не забегая вперед тестов. Но - это того стоит. Во время написания тестов находится много потенциальных логических ошибок, которые в противном случае всплыли бы на этапе выполнения.

В этой статье мы использовали Unit Test Framework, который идет в комплекте с Visual Studio. В следующей статье мы рассмотрим альтернативный - MbUnit.

До новых встреч.

Стимул

Давным-давно по моему летоисчислению я начал вести этот блог. И даже смог из себя выдавить пару статей, одна из которых, даже помогла кому-то. Но, как и все мои другие начинания, мне стало лень/нет времени его вести.

И так бы ничего и не изменилось, если бы не одно событие, показавшее мне, что это все-таки не зря - вести блог. Ходя по форуму sql.ru в перерыве на работе я заметил топик про уже описаный мною продукт - UrlRewriting.NET. И, как это было для меня неожидано, увидел в нем ссылку, ведущую на блог, который вы сейчас читаете.

Вроде мелочь, скажете вы, и я с вами соглашусь. Но эта мелочь - тот стимул, которого мне не хватало.

Этот пост - всего-лишь вступление, попытка вспомнить клавиатуру и разговорный язык - на ближайшее время я наметил пару статей по юнит-тестированию в .NET и целом и в ASP.NET MVC в частности. Также, вероятно, мы с вами коснемся различных паттернов проектирования, как то Repository, IoC и, возможно, других, если я найду их для себя интересными и применимыми в своей реальной жизни.

До скорых встреч, коллеги!

17 сентября 2007

Membership. Смена пароля без ввода старого

Используя стандартный Membership у меня возникла необходимость смены пароля без ввода старого, но к сожалению метод ChangePassword требует два аргумента - старый и новый пароль.

Пришлось обойти это вот так:

string tempPWD = Membership.GetUser().ResetPassword();
Membership.GetUser().ChangePassword(tempPWD, pwdTB.Text);
 

15 января 2007

Nested Repeater - Вложенныйе Repeater

Очень часто при разработке веб-приложений приходится использовать двууровневую иерархию. Например вам нужно динамически вывести категории товаров и сами товары в категориях. Выглядеть это будет примерно так:

  1. Рыба
    1. Форель
    2. Семга
    3. Окунь
  2. Мясо
    1. Говядина
    2. Свинина

Обычно такие списки делаются с помощью Repeater. И действительно все что надо, что бы отобразить категории - пара строк кода:

<asp:Repeater runat="server" ID="catRepeater"> <HeaderTemplate><ul></HeaderTemplate> <ItemTemplate><li><%# DataBinder.Eval(Container.DataItem, "CategoryName")%></li></ItemTemplate> <FooterTemplate></ul></FooterTemplate> </asp:Repeater>

и

protected void Page_Load(object sender, EventArgs e) { SqlConnection conn = new SqlConnection(ConfigurationManager.ConnectionStrings["ConnectionString"].ConnectionString); SqlCommand command1 = new SqlCommand("SELECT CategoryID, CategoryName FROM Categories", conn); SqlDataAdapter adapter = new SqlDataAdapter(command); DataTable dt = new DataTable(); adapter.Fill(dt); catRepeater.DataSource = dt; catRepeater.DataBind(); }

Но как же быть с собственно товарами? Очень просто - добавим вложенный Repeater:

<asp:Repeater runat="server" ID="catRepeater"> <HeaderTemplate><ul></HeaderTemplate> <ItemTemplate><li><%# DataBinder.Eval(Container.DataItem, "CategoryName")%><asp:Repeater runat="server" id="itemRepeater" datasource='<%# GetItemDS(DataBinder.Eval(Container.DataItem,"CategoryID").ToString())%>'> <ItemTemplate><%# DataBinder.Eval(Container.DataItem, "ItemName")</ItemTemplate> </asp:Repeater></li></ItemTemplate> <FooterTemplate></ul></FooterTemplate> </asp:Repeater>

Как видно из листинга второй Repeater самый обыкновенный за исключением параметра DataSource. За счет него при каждой инициализации вложенный Repeater получает новый DataSource относящийся к текущей категории(CategoryID). Это достигается выполнением метода GetItemDS(string categoryID), возвращающим DataTable. В качестве categoryID передается результат выполнения директивы привязки данных DataBinder.Eval(Container.DataItem,"CategoryID").ToString(). Благодаря тому, что первым параметром методу Eval передается Container.DataItem мы получаем CategoryID текущей категори. Вот его код метода GetItemDS:

protected DataTable GetItemDS(string categoryID) { SqlConnection conn = new SqlConnection(ConfigurationManager.ConnectionStrings["ConnectionString"].ConnectionString); SqlCommand command = new SqlCommand("SELECT ItemName FROM Items WHERE CategoryID=@CategoryID", conn); command.Parameter.Add("@CategoryID", SqlDbType.Int).Value = categoryID; SqlDataAdapter adapter = new SqlDataAdapter(command); DataTable dt = new DataTable(); adapter.Fill(dt); return dt; }

Вот собственно и все что нам требуется для достижения результата. Еще раз рассмотрим всю конструкцию. Первоначально мы имеем Repeater который берет данные и выводит категории. В нем мы имеем вложенный Repeater DataSource которого задается динамически с помощью вспомогательного метода GetItemDS и директивы привязки данных с помощью которой в метод передается CategoryID текущей категории. На основании полученной DataTable вложенный Repeater отображает все продукты текущей категории.

Рендеринг веб-контрола по запросу

Иногда в веб-приложении бывает необходимо отослать код, сгенерированный веб-контролом на email пользователю. Чаще всего это встречается в системах со всяческими отчетами. Рассмотрим как это можно реализовать.

Каждый веб-контрол имеет в себе метод , RenderControl(HtmlTextWriter) который при вызове рендерит контрол и возвращает, как результат HtmlTextWriter. Программно это выглядит так:

StringBuilder SB = new StringBuilder(); StringWriter SW = new StringWriter(SB); HtmlTextWriter htmlTW = new HtmlTextWriter(SW); WebControlToRenderID.RenderControl(htmlTW);

Непосредственно к коду вы можете подобраться через метод ToString() объекта HtmlTextWriter.

Однако данный подход прекрасно работающий в ASP.NET 1.1 не подходит для ASP.NET 2.0, так как при выполнении вы получите исключение: RegisterForEventValidation can only be called during Render();

Это происходит потому, что в ASP.NET 2.0 появилась новая возможность - Event Validation. Сделана она для того, чтобы помочь избежать так называемых Injection атак.

К примеру у вас на веб-форме имеется DropDownList контрол:

<asp:DropDownList runat="server" id="MyDropDownList"> <asp:ListItem Value="1">Item 1</asp:ListItem> <asp:ListItem Value="2">Item 2</asp:ListItem> <asp:ListItem Value="3">Item 3</asp:ListItem> </asp:DropDownList>

Если через postback страница передает значение 4, то ASP.NET генерирует исключение, так как ожидает значения 1, 2 или 3. Это идеальное поведение в том случае, если вы действительно хотите получать только 1, 2 или 3, так как если пришло значение 4, это означает, что что-то пошло не так. Но, если вы, к примеру, на клиенте динамически добавляете элемент к полученому select-у, то вас такое поведение не устроит.

В цикле жизни страницы контрол регистрирует себя на подобную проверку через класс ClientScriptManager методом RegisterForEventValidation. Этот метод может быть вызван только на этапе Render, иначе он генерирует исключение. А так как скорее код для програмной генерации контрола вызывается до этого этапа, к примеру, в событии Button1_Click, то вы гарантировано получаете исключение.

Естественно, сказанное выше применимо только к контролам требующим Event Validation. Вот их список:

  1. HtmlAnchor
  2. HtmlButton
  3. HtmlInputButton
  4. HtmlInputCheckBox
  5. HtmlInputHidden
  6. HtmlInputImage
  7. HtmlInputText
  8. HtmlInputPassword
  9. HtmlInputRadioButton
  10. HtmlInputReset
  11. HtmlInputSubmit
  12. HtmlSelect
  13. HtmlTextArea
  14. BulletedList
  15. Button
  16. Calendar
  17. CheckBox
  18. Table
  19. ChildTable
  20. WizardChildTable
  21. DataControlButton
  22. ImageButton
  23. DataControlImageButton
  24. LinkButton
  25. DataControlLinkButton
  26. DataControlPagerLinkButton
  27. DataGridLinkButton
  28. DetailsView
  29. DropDownList
  30. FormView
  31. GridView
  32. HiddenField
  33. ImageMap
  34. LayoutTable
  35. ListBox
  36. Menu
  37. PagerTable
  38. RadioButton
  39. RadioButtonList
  40. TextBox
  41. TreeView
  42. WizardDefaultInnerTable
  43. CatalogZone
  44. ConnectionsZone
  45. EditorZone
  46. WebPartZone
  47. ZoneButton
  48. ZoneLinkButton
Чтобы исправить подобное поведение требуется одно легкое телодвижение. Необходимо вставить, на страницы, рендерящие контрол програмно такой параметр к директиве @Page:

<% @Page EnableEventValidation="False" ... %>

Или же вы можете начинать рендеринг контрола(ов) в событии Page_Render. Перевод статьи Emailing the Rendered Output of an ASP.NET Web Control in ASP.NET 2.0

Url Rewriting в ASP.NET 2

Намедни делая небольшой сайт для знакомой стало ясно что мне понадобится для него модная нынче штука - UrlRewriting. Представляя себе как это делается в общих чертах я воодушевился и полез в Google.

Естественно, первое на что я наткнулся был встроенный в ASP.NET 2 UrlMapping, но почитав про него я понял, что его возможностей для меня будет мало. Сделав еще пару кликов мышью в SERP я набрел на замечательный модуль: UrlRewritingNet.UrlRewrite.

Основными его плюсами можно считать:

  • настройка через web.config с использованием RegEx
  • возможность добавления/изменения Rewrite правил на лету
  • правильная работа с Themes, MasterPages, Postback, Cookieless Sessions
  • необходимость доступа к IIS Manager только для Rewriting урлов без .aspx расширения

Приведу небольшой пример использования, который покажет насколько просто использовать этот модуль. Допустим, у нас есть страница news.apsx, которая выдает новости таким видом: news.aspx?id=13, а мы хотим сделать урл вида news/13.aspx. Для этого добавьте этот код в web.config в секцию <configuration>:

<configSections> <section name="urlrewritingnet" restartOnExternalChanges="true" requirePermission="false" type="UrlRewritingNet.Configuration.UrlRewriteSection, UrlRewritingNet.UrlRewriter" /> </configSections> <urlrewritingnet xmlns="http://www.urlrewriting.net/schemas/config/2006/07" rewriteOnlyVirtualUrls="true" contextItemsPrefix="QueryString" defaultProvider="RegEx"> <rewrites> <add name="News" virtualUrl="~/news/(.*).aspx" rewriteUrlParameter="ExcludeFromClientQueryString" destinationUrl="~/news.aspx?id=$1" ignoreCase="true" /> </rewrites> </urlrewritingnet>

Как видно из данного примера правило Rewrite задается простейшим RegEx выражением. В то же время RegEx дает потенциальную мощность и вы сможете создавать более сложные правила. К примеру такую /news/2007/01/15/url-rewriting.aspx ссылку вы сможете обрабатывать с помщью этого правила:

<rewrites> <add name="News" virtualUrl="~/news/(.*)/(.*)/(.*)/(.*).aspx" rewriteUrlParameter="ExcludeFromClientQueryString" destinationUrl="~/news.aspx?alias=$4" ignoreCase="true" /> </rewrites>

Добро!

Ну что ж начнем. Первый пост комом, как говорится, но все же. Как видно из названия блога - посвящен он будет веб-программированию, а именно ASP.NET 2.0 и все, что с ним связано - Sql Server 2005, AJAX, SEO and more. Так же, если меня сильно заденут, здесь будут пробегать общие новости, вроде выхода iPhone(смайл). Этот блог - моя первая попытка создать нечто не лытдыбровское, а посвященное чему-то доброму, вечному и полезному, так что особо не пинайте. На этом вступление считаю оконченным. Буду рад, если этот блог будет кому-либо полезным.