воскресенье, 27 апреля 2008 г.

Разбираемся с ADO.NET Entity Framework

Итак, в этом проекте мы не будем использовать никаких посторонных систем ORM (про ORM здесь), а попробуем построить слой DAL на основе новой технологии ADO.NET Entity Framework, предложенной Microsoft. Говорят, что это не просто ORM, а нечто большее. Что ж, попробуем понять, что именно. Честно говоря, не совсем понимаю, почему вокруг этой ORM столько шума. Мне, например, очень нравится ORM LLBLGen, я думаю, что эта система ни в чем не уступает майкрософтовской, за исключением, разве что тем, что в ней нельзя использовать LINQ (хотя может быть, и можно).

Entity Data Model
В основе ADO.NET EF лежит модель сущностей, т.е. Entity Data Model. Как и предполагалось, основные объекты модели - это сущности и связи. Все объекты и их связи представлены в виде XML-схемы - в файле с расширением .edmx. Таблицы представлены объектами EntitySet, поля - свойствами Property (обычные поля) и NavigationProperty (поля, участвующие в ассоциациях - ключи, foreign keys).

Служебные объекты (Object Services)
В ADO.NET EF уществует несколько полезных объектов, с помощью которых можно получать доступ к соединению с БД или, например, к выборке из базы, представленной в виде типизированной коллекции.
ObjectContext позволяет получить доступ к соединению, метаданным и еще кое-каким службам.
ObjectQuery - это, по сути, запрос, который возвращает коллекцию типизированных сущностей.
ObjectStateManager кэширует запросы, а также отслеживает изменения в сущностях (с целью дальнейшей синхронизацией с БД).

Mapping
Это то, как полученные сущности связаны с реальными объектами базы данных.

Как все это выглядит
Добавим в наш солюшн еще один проект, который назовем DataCore типа ClassLibrary. Затем в проект добавим новый айтем типа ADO.NET Entity Data model. Сразу же на экране возникает визард, предлагающий нам выбрать, будет ли наша модель построена на основе базы данных, или она будет пустой. Конечно, выбираем Generate from database. Дальше все просто. Визард автоматически подключается к серверу, вернее, сразу к самой базе, после чего мы выбираем таблицы и другие объекты, которые хотим включить в модель. В нашем проекте мы включим следующие таблицы:

  • aspnet_Applications

  • aspnet_Membership

  • aspnet_Users

  • Category

  • Comment

  • Question

  • UserDetails


Ни представления, ни хранимые процедуры пока включать в модель не будем. После этого жмем Finish и вот перед нами диаграмма сгенерированных сущностей. Вот как она выглядит:


Если теперь выбрать файл DataModel.edmx в SolutionExplorer, а затем Open with... XML Editor, то мы увидим, как в действительности выглядит этот файл. Это обычный XML, точнее схема, где описаны все элементы: сущности идут здесь как EntitySet, связи между таблицами (ассоциации) как AssociationSet, а отдельные поля - это Properties.

Помимо этой схемы, студия генерирует еще три файла DataModel.csdl, DataModel.msl и DataModel.ssdl. Файл CSDL представляет собой концептуальную схему - это XML файл, в котором представлены объекты на уровне сущностей. Файл SSDL - схема объектов (таблиц и пр.), как они представлены в базе данных.Файл MSL - схема сопоставления - содержит данные о том, как связаны между собой концептуальная схема и схема объектов БД.

Теперь вернемся в Solution Explorer и рассмотрим файл DataModel.Designer.cs. Это самый важный для нас файл, потому что здесь сосредоточены все сгенерированные объекты. Если мы откроем наш первый проект (MVC), то в любом месте сможем, например, написать (не забудьте добавить ссылку на проект):

using (DataCoreConnection context = new DataCoreConnection())
{
Category cat = new FAQEngineModel.Category();
cat.Name = "Web";
context.AddObject("Category",cat);
context.SaveChanges();
}

Итак, что же делает этот код?
В первую очередь он создает объект контекста (DataCoreConnection), который будет уничтожен после выхода за пределы фигурных скобок. Этот объект идет первым в нашем файле DataModel.Designer.cs и является по сути связующим звеном между нашим кодом (объектами) и базой данных. С его помощью выполняются все стандартные операции с данными - выборка, вставка, обновление и удаление.

Затем мы создаем новый объект категории, присваиваем ей имя Web, добавляем объект в контекст и сохраняем изменения. Это, конечно, всего лишь пример кода, поскольку настоящий код должен обязательно содержать конструкцию try-catch.

Однако в этом коротком примере кроется одна проблема, которая, как я надеюсь, будет в дальнейшем решена разработчиками студии. Дело в том, что все проходит гладко, когда мы имеем один проект, из которого вызываем наш контекст. Но как только мы выделяем обработку с данными в отдельный проект, а вызываем код из другого проекта, начинаются проблемы. Во-первых, поскольку вызывающий проект (у нас это веб-проект) имеет свой файл конфигурации, в котором содержатся свои строки подключения к БД, он в упор не видит файла конфигурации App.config проекта DataCore, и следовательно, не может подключиться к базе. Как результат, мы видим на экране сообщение EntityConnection error: The specified named connection is either not found in the configuration. Это исправляется легко - достаточно скопировать строку подключения из файла App.config в файл Web.config нашего веб-проекта.

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

<add name="DataCoreConnection" connectionString="metadata=.\FAQEngineModel.csdl|.\FAQEngineModel.ssdl|.\FAQEngineModel.msl;provider=System.Data.SqlClient;provider connection string="Data Source=.\SQLEXPRESS;Initial Catalog=FAQEngine;Integrated Security=True;MultipleActiveResultSets=True"" providerName="System.Data.EntityClient" />


Обратите внимание, на какие файлы метаданных она ссылается. Все эти файлы лежат в каталоге bin/Debug проекта DataCore (.\) - т.е. в каталоге, куда помещается dll. Но поскольку мы вызываем эту dll из другого проекта, то получается, что мы ищем эти файлы там, где их нет, и получаем новую ошибку: "The specified metadata path is not valid. A valid path must be either an existing directory, an existing file with extension '.csdl', '.ssdl', or '.msl', or a URI that identifies an embedded resource.". Нам остается два выхода - либо исправить строку подключения ,либо каждый раз копировать нужные файлы в какой-либо каталог веб-проекта.

Попробуем сначала первый способ. Лучше всего, если бы можно было указать относительный путь, например, с использованием какого-нибудь макроса. Единственное, что мне удается найти - это DataDirectory. Но, поскольку мы вызываем файлы из веб-приложения, этот макрос будет заменен каталогом App_Data, который нам вовсе ни к чему. Поэтому можно просто указать абсолютный путь к файлам на диске. Но потом, после деплоймента, эту строку придется менять. Поэтому этот способ мне не нравится.

Второй способ - копирование файлов - тоже не слишком хорош. Получается, что каждый раз, как только я изменяю модель, мне надо куда-то еще копировать сгенерированные файлы? Получается, что так... ладно, пусть это делает за меня студия. Открываем свойства проекта DataCore, идем в Post-Build event и пишем следующее:


copy $(TargetDir)FAQEngineModel.csdl $(SolutionDir)FAQEngine\App_Data\
copy $(TargetDir)FAQEngineModel.msl $(SolutionDir)FAQEngine\App_Data\
copy $(TargetDir)FAQEngineModel.ssdl $(SolutionDir)FAQEngine\App_Data\


Здесь мы просто копируем нужные файлы в каталог App_Data нашего веб-приложения. Теперь остается чуть подправить строку подключения (в файле Web.config):

<add name="DataCoreConnection" connectionString="metadata=|DataDirectory|\FAQEngineModel.csdl||DataDirectory|\FAQEngineModel.ssdl||DataDirectory|\FAQEngineModel.msl;provider=System.Data.SqlClient;provider connection string="Data Source=.\SQLEXPRESS;Initial Catalog=FAQEngine;Integrated Security=True;MultipleActiveResultSets=True"" providerName="System.Data.EntityClient" />

Все работает!
Решение данной проблемы описано на форуме ADO.NET Entity Framework.


Ссылки по теме:
Обзор ADO.NET Entity Framework
Блог Sergey Rozovik - посты, посвященные EF
Работа с макросами в Pre- и Post-Build events

суббота, 12 апреля 2008 г.

Microsoft MVC Framework - роутинг

Правила
В основе роутинга в MS MVC Framework лежит следующий принцип - "мы не обращаемся к файлу, мы обращаемся к классу контроллера, и делаем это через паттерны". Что здесь имеется в виду? А то, что мы действительно не вызываем никакие файлы, т.е. мы пишем в адресной строке не mysite/products/details.aspx?productid=6, а вместо этого указываем "дружественный" URL - mysite/products/details/6. Второй момент - это, конечно, никакой не URL. Т.е. слэши здесь играют роль разделения не папок (как в настоящем URL), а частей (элементов) паттерна. Суть в том, что запрос на сервере разбирается, и каждая его часть (отделенная слэшем) интерпретируется так, как вы задали в паттерне.

Паттерн (или маршрут) это последовательность элементов-заполнителей (placeholders), в соответствие с которыми производится разбор строки запроса. Паттерн может содержать не только некоторые элементы-переменные, но и константные значения. Каждый элемент заключается в фигурные скобки { и }. Элементы отделяются друг от друга слэшем или точкой. Все, что не заключено в фигурные скобки, воспринимается как константа. Типичный пример паттерна:

{controller}/{action}/{id}

А вот строка, соответствующая этому паттерну:

Products/Details/55

Для того, чтобы приложение знало о том, как обрабатывать запрос, паттерн надо зарегистрировать. Лучше всего его регистрировать в событии Application_Start в файле Global.asax. Если вы хотите, чтобы регистрация паттерна была доступна и в проекте, предназначенном для тестирования, то метод регистрации должен быть статическим и принимать в качестве параметра RouteCollection.
Например, регистрация маршрута может выглядеть так:

protected void Application_Start(object sender, EventArgs e)
{
RegisterRoutes(RouteTable.Routes);
}

public static void RegisterRoutes(RouteCollection routes)
{
routes.Add(new Route
(
"Category/{action}/{categoryName}"
, new CategoryRouteHandler()
));
}


Какие запросы могут подходить под этот паттерн? Например, localhost/Category/View/Yachts. А вот запрос localhost/Category/View не подходит, поскольку во-первых, в строке не хватает третьего элемента, а во-вторых, потому, что паттерн не предусматривает дефолтного значения для него (об этом чуть ниже).
Следует учесть, что в процессе регистрации все паттерны складываются в коллекцию, из которой извлекаются в порядке очереди. Это значит, что размещать паттерны надо по степени убывания специализации, как кэтчи в блоке try-catch, и дефолтный паттерн должен идти последним.

Теперь посмотрим, как задаются дефолтные значения для элемента (они подставляются в случае отсутствия элемента в строке запроса):


void Application_Start(object sender, EventArgs e)
{
RegisterRoutes(RouteTable.Routes);
}

public static void RegisterRoutes(RouteCollection routes)
{
routes.Add(new Route
(
"Category/{action}/{categoryName}"
new CategoryRouteHandler()
)
{
Defaults = new RouteValueDictionary
{{"categoryName", "food"}, {"action", "view"}}
}
);
}


Как видим, все достаточно просто - если отсутствует имя категории, подставляем food, если нет action, подставляем view.

Если мы не знаем заранее точно, сколько элементов может содержать строка запроса, но хотим все же как-то обрабатывать запрос, то можно записать паттерн следующим образом:
"Category/{action}/{*titles}" . Звездочка указывает на то, что начиная с этого элемента число последующих элементов может быть любым. Но при этом все они будут трактоваться как третий элемент. Например, для строки запроса localhost/Category/View/Maxi/55 Category - это Category, action - это View, а titles - это Maxi/55. Что делать с таким параметром, мы уже решаем сами.

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

void Application_Start(object sender, EventArgs e)
{
RegisterRoutes(RouteTable.Routes);
}

public static void RegisterRoutes(RouteCollection routes)
{
routes.Add(new Route
(
"{locale}/{year}"
, new ReportRouteHandler()
)
{
Constraints = new RouteValueDictionary
{{"locale", "{a-z}{2}-{A-Z}{2}"},{"year", @"\d{4}"}}
});
}

Мы видим, что элемент year должен содержать 4 цифры, только в этом случае он будет распознан.

Итак, главное, что надо уяснить с роутингом - это то, что строка запроса всегда разбивается ровно на три логические части - controller, action и id. Если вам нужно передать контроллеру (вернее, его методу) параметров больше одного, то вы просто удлиняете запрос, как будто добавляете еще одну папку, а уже в методе разбиваете параметр на нужное количество.

IIS 6 и II7 - замечания по использованию
Необходимо учитывать момент, связанный с тем, какую именно версию IIS вы используете. Пока мы тестируем наше приложение с помощью сервера, встроенного в студию, все идет хорошо. Но как только мы запускаем приложение через IIS 6, то все перестает работать - адреса попросту не находятся.


Кстати, если при попытке отладки через сервер у вас начнут вылезать странные ошибки вроде Failed to access IIS metabase или Unable to start debugging on the web server. The web server is not configured correctly., просто снесите asp.net 2 и установите его заново - aspnet_regiis.exe -u, потом aspnet_regiis.exe -i .


Да, речь идет только об IIS6. Потому что IIS7 уже умеет работать с приложениями, основанными на MVC Frm, и понимает запросы без расширений файлов, а шестой еще нет. Поэтому надо ему помочь. Для этого надо в код регистрации паттерна включить версию для IIS6. Но для начала в конфиге пропишем явно, на какой версии IIS запускаем наше приложение. Поскольку этот параметр может быть изменен (например, после деплоймента на рабочий сервер), заносим этот момент в наш список TODO. Вот примерный код:

<add key="IISVersion" value="6" />

Теперь в код Global.asax добавляем код, с помощью которого сначала производим выбор версии IIS:

protected void Application_Start(object sender, EventArgs e)
{
RegisterRoutes(RouteTable.Routes);
}
public static void RegisterRoutes(RouteCollection routes)
{
int iisVersion = Convert.ToInt32(ConfigurationManager.AppSettings["IISVersion"], System.Globalization.CultureInfo.InvariantCulture);

if (iisVersion >= 7)
{
RegisterRoutesForIIS6(routes);
}
else
{
RegisterRoutesForIIS7(routes);
}
}


Затем мы в соответствующих методах регистрируем наши паттерны. Код в методах отличается только наличием расширения .mvc для IIS6. Мне не очень нравится подобное дублирование, но ничего лучше мне придумать не удалось (если придумаете, напишите мне, а я расскажу здесь о вашей идее - с сохранением всех копирайтов, разумеется). Итак, получился примерно такой код:


private static void RegisterRoutesForIIS7(ICollection routes)
{
routes.Add(new Route("{controler}/{action}/{id}", new MvcRouteHandler())
{
Defaults = new RouteValueDictionary(new { controller = "Home", action = "Index", id = "" }),
});
...
}
private static void RegisterRoutesForIIS6(ICollection routes)
{
routes.Add(new Route("{controler}.mvc/{action}/{id}", new MvcRouteHandler())
{
Defaults = new RouteValueDictionary(new { controller = "Home", action = "Index", id = "" }),
});
...
}

Неудобство использования IIS6 проявляется и в том, что нормальные адреса (как предполагалось, без расширений) все равно не получаются. Более того, скажу честно, мне так и не удалось заставить всю эту кухню работать под IIS6. На встроенном в студию сервере - пожалуйста, а под IIS6 - ни в какую. Однако не будем терять надежды. Во-первых, может быть, это всего лишь баг, и его в релизе исправят, а во-вторых, может, к тому времени на серверах отомрут IISы-6...

Хозяйке на заметку

С контроллерами мы будем разбираться позже, сейчас же буквально два слова о том, каким образом MVC Frm узнает о том, что и кому передавать. В основе всего лежит объект System.Web.Routing.UrlRoutingModule, который реализует ничто иное, как интерфейс IHttpModule. Http-модуль характеризуется тем, что вставивается в конвейер обработки запросов. Данный модуль перехватывает запросы и интерпретирует их по-своему. Этот модуль, как полагается, зарегистрирован в файле конфига:

<httpModules>
<add name="ScriptModule" type="System.Web.Handlers.ScriptModule, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>
<add name="UrlRoutingModule" type="System.Web.Routing.UrlRoutingModule, System.Web.Routing, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>
<httpModules>


А Http-хандлером является MvcHandler, он решает, какой контроллер вызывать и, соответственно, какой метод. Надо знать, что если в строке запроса указан контроллер, например, Products, то реально сам контроллер (это вообще класс) должен называться ProductController. Имя метода же должно точно соответствовать элементу action запроса.

Полезные ссылки:
Kigg - первый стартер-кит, построенный на MS MVC Framework (клон Digg'a)
Еще кое-что о роутинге

Куда дальше?
В следующей части я рассмотрю архитектуру приложения.

пятница, 11 апреля 2008 г.

Microsoft MVC Framework - первый взгляд

Итак, что же это такое - Microsoft MVC Framework ?
Многие (а может быть и все) из нас знают, что совет "разделяй и властвуй" как нельзя лучше подходит к программированию. Веб-программирование проделало долгий путь, постепенно приближаясь к воплощению данного совета в жизнь. Вспомним классический ASP, в котором текст страницы перемежался с кодом, или файлы Perl, в которых, наоборот, код перемежается раздражающими вставками разметки. С появлением ASP.NET и модели "code-behind" кажется, мы приблизились к заветному разделению - код в одном файле, разметка в другом. И все же нет в мире совершенства... лично меня периодически раздражал тот факт, что в коде, обрабатывающем какой-нибудь Postback, надо обращаться к базе (или к слою DAL), и тут же - к элементам интерфейса, и все перемешивается в одну кучу...

Кажется, MVC призвана решить именно эту проблему. Во-первых, что такое MVC? Это такой принцип разработки (он же паттерн), при котором все разделено: интерфейс (View), управление (Controller) и внутренняя логика и данные (Model). Подробнее описано в википедии. Там же, кстати, есть ссылки на более подробные статьи на эту тему.

Microsoft MVC Framework решает данную задачу по-своему, и сейчас мы попробуем разобраться, как именно. В контексте этого фреймворка Model - это некий набор классов, позволяющих манипулировать данными и логикой. Например, это могут быть классы, представляющие некоторые сущности (entities). View - это, естественно, интерфейс - т.е. файл страницы с разметкой. Наконец, Controller - это классы, с помощью которых осуществляется взаимодействие с пользователем, обработка пользовательских данных. Пока не очень понятно, что за всем этим стоит (кроме представления).

Обработка урлов в MVC идет по-особенному, т.е. сервер обращается не к конкретной странице, как это было в ASP.NET, а к классу контроллера, и вот этот-то контроллер и обрабатывает запрос пользователя, а также пользовательский ввод. Кроме того, он, с одной стороны, обращается к модели (т.е. использует логику и данные), с другой стороны, вызывает уже определенный класс представления, т.е., попросту говоря, файл .aspx. С этими процессами связан один интереснейший момент, а именно routing, позволяющий в адресной строке браузера указывать не имена файлов, а путь к некоторому вложенному каталогу (на самом деле это не каталоги, но об этом позже). Т.е. вместо Products/Product.aspx?id=5 мы указываем Products/Details/5. Очень удобно и красиво, а самое главное, просто! Если кто-то пытался сделать подобное в классическом ASP.NET, он сталкивался с невероятными трудностями, о преодолении которых можно почитать в MSDN (URL Rewriting in ASP.NET). Организацию роутинга мы подробно разберем позже, а сейчас продолжим исследование MVC.

Еще одна очень любопытная деталь - в MVC полностью отказались от постбэков. Это означает две вещи: во-первых, поскольку каждый раз обращение идет к определенному классу контрола, он вызывает конкретную страницу, и следовательно, эта страница загружается всегда заново; а во-вторых, как следствие, здесь не существует такого понятия, как жизненный цикл страницы, вернее, он существует, но нам совершенно не интересен - мы не можем использовать Viewstate и события, связанные с жизненным циклом...


Полезные ссылки:
дневник ScottGu по теме
Создание веб-приложений без форм
ASP.NET Model View Controller Applications

четверг, 10 апреля 2008 г.

MVC, Entity Framework и Membership provider. Подготовка.

Чтобы в дальнейшем работать с пользователями - вход, регистрация, выход, администрирование и т.д. - необходимо использовать какой-то интерфейс. Самым удобным мне кажется Membership provider. Но чтобы заставить работать и его, надо проделать некоторую предварительную работу, о которой я и расскажу.
Всякие установки
Начнем с того, что установим VS 2008, который достался совершенно бесплатно на конференции разработчиков. В пакет также входит SQL Server 2005, а SQL Server Management Studio Express можно также скачать бесплатно.
Теперь надо установить необходимые дополнения - MVC Framework (здесь) и ADO.NET Entity Framework (здесь). После установки при создании нового приложения выбираем Web -> ASP.NET MVC Web Application. Все, студия создала для нас заготовку приложения!
Двигаемся дальше.

База данных
Создаем в студии SQL Server новую базу. После этого в Web.config нашего приложения добавляем строку подсоединения к базе, например:

<add name="Main.ConnectionString" connectionString="Data Source=5D734033\SQLEXPRESS;Initial Catalog=DB;Integrated Security=True;User Instance=True" providerName="System.Data.SqlClient" />

Строку эту, естественно, добавляем в раздел connectionStrings.

Membership
Теперь добавляем настройки для нашего провайдера. Например, такие:

<membership defaultProvider="FProvider" userIsOnlineTimeWindow="1">
<providers>
<clear />
<add name="FProvider"
applicationName="FEngine"
connectionStringName="Main.ConnectionString"
type="System.Web.Security.SqlMembershipProvider"
enablePasswordRetrieval="false"
enablePasswordReset="true"
minRequiredPasswordLength="6"
minRequiredNonalphanumericCharacters="0"
passwordFormat="Hashed"
requiresQuestionAndAnswer="false"
requiresUniqueEmail="true" />
</providers>
</membership>

Далее создаем настройки для провайдера ролей. Например, так:

<roleManager enabled="true"
defaultProvider="rprovider"
cookieName="RC"
cookieTimeout="30"
cookieSlidingExpiration="true"
cookieProtection="All"
cacheRolesInCookie="false"
>
<providers>
<add name="rprovider"
type="System.Web.Security.SqlRoleProvider"
applicationName="FEngine"
connectionStringName="Main.ConnectionString"/>
</providers>
</roleManager>
Все эти настройки нужны для того, чтобы Membership API знал, откуда брать все данные. Если этого не сделать, то он создаст свою собственную базу данных, но нам ведь не это надо, верно?

Заодно не забываем установить authentication mode равным Forms.
Теперь выполним важный шаг, о котором я практически всегда забываю. Надо сгенерировать все необходимые таблицы - для этого находим утилиту aspnet_reqsql.exe и запускаем ее. Эта утилита лежит всегда в одном месте - C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727 .
В результате откроется вполне нормальное окошко Windows, где мы сначала нажимаем Next, потом выбираем Configure SQL Server for application services, снова нажимаем Next. Здесь нас просят выбрать сервер базы, тип аутентификации (обычно Windows, особенно на локальном компьютере), имя базы. Сервер у меня определился неправильно, поэтому сначала выскочило сообщение об ошибке. После введение в поля правильного имени в выпадающем списке появились все базы. Выбираем базу, жмем Next, потом опять Next, потом Finish.

Проверим, все ли получилось. Зайдем на сервер, выберем нашу базу данных и посмотрим ее содержимое. Если все в порядке, то в базе должна появиться куча новых таблиц, названия которых начинаются с aspnet_, например, aspnet_Roles.
Вот теперь можно проверить, работает ли наш мембершип провайдер. Для этого возвращаемся в студию, компилируем проект, затем выбираем Project ->ASP.NET Configuration. В результате студия сгенерирует и запустит специальный проект, с помощью которого можно делать некоторые административные вещи, например, устанавливать роли, создавать пользователей. Не думаю, что кто-нибудь пользуется им для реальной работы, но для проверки и установки начальных параметров вполне сойдет.
Итак, после некоторой паузы наконец открывается окно браузера с этим проектом. Внешне все выглядит нормально, но зайдем на вкладку Security. Так и есть! Сообщение об ошибке:

There is a problem with your selected data store. This can be caused by an invalid server name or credentials, or by insufficient permission. It can also be caused by the role manager feature not being enabled. Click the button below to be redirected to a page where you can choose a new data store.

The following message may help in diagnosing the problem: Cannot open database "FEngine" requested by the login. The login failed. Login failed for user 'S-5D7E4033\S'.

Итак, делаю две вещи:
Во-первых, изменяю в свойствах сервера тип аутентификации с Windows на смешанный.
Во-вторых, убираю из строки соединения в файле web.config параметр User Instance=True. Нам это совсем не нужно (насколько я знаю, этот параметр позволяет запускать локальную версию сервера).

Снова компилируем, запускаем проект конфигурации, и ура! - все работает, это видно даже на первой странице, где появилась информация о количестве существующих пользователей (0).

Идем на вкладку Provider и щелкаем ссылку Select a different provider for each feature (advanced). В результате оказываемся на странице, которая показывает нам отмеченными оба провайдера. Значит, все в порядке.

Идем на вкладку Sequrity и создаем необходимые роли. Я пока вижу только две роли: admin и member. Создаем обе роли, затем пару пользователей - один пусть будет админ, другой - member, т.е. зарегистрированный пользователь.
На этом подготовительную работу можно считать сделанной.

О чем этот блог

Этот блог посвящен программированию, точнее, одному проекту, который я в данное время и разрабатываю. Проект этот делается на основе достаточно новых технологий - таких, как Microsoft MVC Framework и ADO.NET Entity Framework. По мере разработки я буду писать в блог - описывать очередной этап и проблемы, с ним связанные.
Надеюсь, он (т.е. блог) поможет вам не наступать на те же самые грабли.
Поехали...