…В примерах этого нет, но читатель легко это реализует сам…
ulu, статья на хабре 

Недавно пришлось реализовывать тот самый “smart way” отображения выпадающий списков, с поправкой на ASP.NET MVC 3. Это пост – короткий чеклист для реализации.

Попробуем отобразить выпадающий список для нескольких полей модели.

Основная модель:

ModelsMovie.cs

public class Movie
{
   
public int Id { get; set
; }
   
public int GenreId { get; set
; }
   
public int Year { get; set; }
}

Контроллер с двумя действиями – отображение формы и создание Movie:

ControllersMovieController.cs

public class MovieController : Controller
{
    [
HttpGet
]
   
public ActionResult
Create()
    {
       
return
View();
    }

    [
HttpPost
]
    [
ActionName("Create"
)]
   
public ActionResult CreateMovie(Movie
model)
    {
       
if (!this
.ModelState.IsValid)
        {
           
return
View(model);
        }
       
else
        {
           
// TODO: save model
            // this.Repository.Save(model);

           
return this.RedirectToAction("Details"
,
               
new
{ id = model.Id });
        }
    }

    [
HttpGet
]
   
public ActionResult
Details()
    {
       
// sample code to show the same Create form with preset values
        return View("Create", new Movie() { GenreId = 2, Year = 2000 });
    }
}

Представление для создания Movie. Создано стандартным scaffold-ом. Подключение скриптов валидации перенесено в Views/Shared/_Layout.cshtml:

ViewsMovieCreate.cshtml

@model MvcDropDowns.Models.Movie

@{
    ViewBag.Title =
"Create a Movie"
;
}

<h2>Create a Movie</h2>

@
using
(Html.BeginForm()) {
    @Html.ValidationSummary(
true
)
   
<fieldset>
        <legend>Movie</legend>

       
<div class="editor-label">
            @Html.LabelFor(model => model.GenreId)
       
</div>
        <div class="editor-field">
            @Html.EditorFor(model => model.GenreId)
            @Html.ValidationMessageFor(model => model.GenreId)
       
</div>

       
<div class="editor-label">
            @Html.LabelFor(model => model.Year)
       
</div>
        <div class="editor-field">
            @Html.EditorFor(model => model.Year)
            @Html.ValidationMessageFor(model => model.Year)
       
</div>

       
<p>
            <input type="submit" value="Create" />
        </p>
    </fieldset>
}

Собираем, запускаем. Видим два текстовых поля. При нажатии на кнопку Create срабатывают клиентские валидаторы – поля не nullable.

image

Начнем превращать текстбоксы в выпадающие списки. Для начала, добавим UIHint и еще пару атрибутов для более человеческого отображения:

ModelsMovie.cs

public class Movie
{
   
public int Id { get; set
; }
       
    [
UIHint("Genre"
)]
    [
Display(Name="Genre"
)]
    [
DisplayFormat(NullDisplayText="---Please Select---"
)]
   
public int GenreId { get; set
; }

    [
UIHint("Year"
)]
    [
DisplayFormat(NullDisplayText="---Please Select---"
)]
   
public int Year { get; set; }
}

Следующий шаг – добавление шаблонов редактирования, с именами Genre и Year:

ViewsSharedEditorTemplatesGenre.cshtml

@model int?
@{
    Html.RenderAction(
"Genres", "List");
}

ViewsSharedEditorTemplatesYear.cshtml

@model int?
@{
    Html.RenderAction(
"Years", "List");
}

Единственное назначение этих шаблонов – вызов соответствующего действия контроллера List. Все остальное, включая имена полей, контроллер в состоянии получить самостоятельно. Основной соблазн при реализации – захардкодить название полей, или часть метаданных. Постараемся этого не допустить :)

ControllersListController.cs

public class ListController : Controller
{
   
public ActionResult
Genres()
    {
       
int? selectedGenreId = this.ControllerContext.ParentActionViewContext.ViewData.Model as int
?;

       
var genres = new List<Genre
>
        {
           
new Genre { Id = 1, DisplayName = "Comedy"
},
           
new Genre { Id = 2, DisplayName = "Horror"
},
           
new Genre { Id = 3, DisplayName = "Documentary"
}
        };

       
var model = new SelectList(genres, "Id", "DisplayName"
, selectedGenreId);

       
this
.ViewData.Model = model;
       
this.ViewData.ModelMetadata = this
.ControllerContext.ParentActionViewContext.ViewData.ModelMetadata;

       
return View("DropDown"
);
    }

   
public ActionResult
Years()
    {
       
int? selectedYear = this.ControllerContext.ParentActionViewContext.ViewData.Model as int
?;

       
var model = new SelectList(Enumerable
.Range(1990, 20), selectedYear);

       
this
.ViewData.Model = model;
       
this.ViewData.ModelMetadata = this
.ControllerContext.ParentActionViewContext.ViewData.ModelMetadata;

       
return View("DropDown");
    }
}

Последний этап – добавление представления DropDown. Вызываем Html.DropDown, передавая ему все подряд:

ViewsListDropDown.cshtml

@model SelectList
@{
   
var templateInfo = this
.ViewContext.ParentActionViewContext.ViewData.TemplateInfo;
}
@
Html.DropDownList(
    templateInfo.GetFullHtmlFieldName(
""
),
   
this
.Model,
   
this
.ViewData.ModelMetadata.NullDisplayText,
   
new
    {
        id = templateInfo.GetFullHtmlFieldId(
"")
    })

После запуска мы должны увидеть полноценные выпадающие списки:

image

У предложенной реализации есть один серьезный баг. Серверные валидаторы работают (как и раньше). Клиентские – не работают совсем. О том, как их починить, рассказано во второй части.

Исходный код к этой статье: MvcDropDowns.zip. Требует ASP.NET MVC 3.0 RC2 для запуска.

Между двумя методами работы с транзакциями есть одно незаметное, очень существенное отличие.

SqlConnection.BeginTransaction создает транзакцию с уровнем изоляции по умолчанию – обычно это READ COMMITTED или READ COMMITTED SNAPSHOT.

Попытка выполнить команду в TransactionScope так же приводит к созданию транзакции SQL, но уже с уровнем изоляции SERIALIZABLE.

Внезапная разница между уровнями изоляции обеспечивает неповторимые ощущения при разрешении дедлоков :) Будьте бдительны!

Пару дней назад поймал и починил баг с зацикливанием на многопоточной работе с Dictionary. По счастливой случайности услышал именно об этой особенности стандартного Dictionary пару недель назад, из доклада Алексея Недилько на devcamp :)

Сам баг подробно описан у Tess. Что не помешало прожить ему в проекте около 3-х лет. Самое забавное – баг жил в codebehind у ErrorPage.aspx и проявлялся при появлении нескольких одновременных специфических ошибок :)

Забавный сложился порядок выпуска очередного релиза

  1. Залить на хостинг (во вторник).
  2. Обкатать (в среду)
  3. Начать использовать новую версию для триалов.
  4. Начать ставить новую версию для новых платных аккаунтов.
  5. Запустить обновление существующих аккаунтов. Подождать два дня.
  6. Сделать официальную презентацию для Sales. 
  7. Прислать уведомление о выходе новой версии. Подождать день (сейчас пятница, релиз на этой стадии :).
  8. Выложить наконец-то новую версию для скачивания.
  9. Обновить новости на сайте.

Самое забавное, что Sales и Support с канадской стороны узнают о релизе позже пользователей. По крайней мере, официально. :)