*Это гостевая публикация от Long Le, архитектора корпоративных решений и решения архитектора в Computer Sciences Corporation. Оригинально опубликовано на личном блоге Long, Long — активный блогер и серьезный геймер Modern Warfare 3. Обязательно следите за его кодом и разговорами о играх в Twitter @LeLong37 и проверьте его предыдущую публикацию на блоге Twilio. *
Я привык писать приложения Mvc с использованием IoC, в частности с Ninject и Managed Extensibility Framework (Mef), и мне было интересно посмотреть, как мы можем внедрить большинство наших экземпляров Twilio (типов TwilioResponse и TwilioRestClient) при использовании их в моем контроллере WebApi Mvc 4.
Чтобы «MEFизировать» ваше приложение Mvc 4 .NET 4.0, скачайте проект Mvc4.Mef и добавьте ссылку на него в вашем веб-приложении.
Давайте быстро рассмотрим некоторые важные классы в этом проекте (Mvc.Mef), которые выполняют тяжелую работу по подключению Mef в вашем приложении Mvc.
**MefMvcConfig.cs**
— Создание нашего CompositionContainer, контейнера, который содержит все наши экспорты и импорты.
— AggregateCatalog, который представляет собой коллекцию каталогов, в нашем случае состоит из ComposableCatalog (мы передаем TypeCatalog позже из нашего приложения Mvc, который наследует Composable Catalog) и DirectoryCatalog, которые являются возможными классами, которые могут быть украшены атрибутами [Export] и [Import] из текущей сборки, а также из других сборок, которые могут быть в папке bin из наших других проектов.
Примечание: Метод RegisterMef будет вызван из вашего Application_Start(), Global.asax.cs класса для интеграции и подключения Mef в ваше приложение Mvc.
[sourcecode language=»csharp»]
public static class MefMvcConfig
{
public static void RegisterMef(TypeCatalog typeCatalog)
{
var compositionContainer = ConfigureContainer(typeCatalog);
ServiceLocator
.SetLocatorProvider(() => new MefServiceLocator(compositionContainer));
ControllerBuilder
.Current.SetControllerFactory(
new MefMvcControllerFactory(compositionContainer));
GlobalConfiguration
.Configuration
.DependencyResolver =
new MefMvcDependencyResolver(compositionContainer);
}
private static CompositionContainer ConfigureContainer(
ComposablePartCatalog composablePartCatalog)
{
var path = HostingEnvironment.MapPath(«~/bin»);
if (path == null) throw new Exception(«Unable to find the path»);
var aggregateCatalog = new AggregateCatalog(new DirectoryCatalog(path));
if (composablePartCatalog != null)
aggregateCatalog.Catalogs.Add(composablePartCatalog);
return new CompositionContainer(
aggregateCatalog,
new MefNameValueCollectionExportProvider(
ConfigurationManager.AppSettings));
}
}
[/sourcecode]
**MefMvcControllerFactory.cs**, который наследует DefaultControllerFactory и устанавливает наш собственный контроллер-фабрику, которая использует наш CompositionContainer для разрешения типов контроллеров.
[sourcecode language=»csharp»]
public class MefMvcControllerFactory : DefaultControllerFactory
{
private readonly CompositionContainer _compositionContainer;
public MefMvcControllerFactory(CompositionContainer compositionContainer)
{
_compositionContainer = compositionContainer;
}
protected override IController GetControllerInstance(
RequestContext requestContext, Type controllerType)
{
var export = _compositionContainer
.GetExports(controllerType, null, null).SingleOrDefault();
IController result;
if (null != export)
result = export.Value as IController;
else
{
result = base.GetControllerInstance(requestContext, controllerType);
_compositionContainer.ComposeParts(result);
}
return result;
}
}
[/sourcecode]
**MefMvcDependencyResolver.cs**, который используется для переопределения разрешения зависимостей в нашем приложении WebApi.
[sourcecode language=»csharp»]
public class MefMvcDependencyResolver : IDependencyResolver
{
private readonly CompositionContainer _compositionContainer;
public MefMvcDependencyResolver(CompositionContainer compositionContainer)
{
_compositionContainer = compositionContainer;
}
#region IDependencyResolver Members
public IDependencyScope BeginScope()
{
return this;
}
public object GetService(Type type)
{
var export = _compositionContainer
.GetExports(type, null, null).SingleOrDefault();
return null != export ? export.Value : null;
}
[/sourcecode]
Теперь скачайте проект Mvc4.Mef.Framework и добавьте ссылку на него в ваше приложение Mvc 4. Добавьте одну строку в ваш файл Global.asax.cs, чтобы все это настроить.
Примечание: обратите внимание, что мы передаем TypeCatalog, когда вызываем метод RegisterMef, это необходимо для того, чтобы мы могли передать закрытые классы (типы), которые мы хотим внедрить или разрешить с помощью внедрения зависимостей с помощью Mef, в данном случае объект TwilioResponse из их Api.
Global.asax.cs
[sourcecode language=»csharp» highlight=»7,14,15,16,17,19,20,21,22,23″]
public class MvcApplication : HttpApplication
{
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
MefMvcConfig.RegisterMef(new TypeCatalog(typeof(MefAdapter)));
WebApiConfig.Register(GlobalConfiguration.Configuration);
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
GlobalConfiguration
.Configuration
.Formatters
.XmlFormatter
.AddUriPathExtensionMapping(«xml», «text/xml»);
GlobalConfiguration
.Configuration
.Formatters
.XmlFormatter
.AddUriPathExtensionMapping(«json», «application/json»);
}
}
[/sourcecode]
Теперь, если мы запустим наше приложение, вы должны увидеть, что запрос из HomeController теперь разрешается через наш MefMvcControllerFactory из нашего CompositionContainer.
Чтобы Mef мог обслужить и разрешить эти контроллеры, мы должны украсить наши контроллеры атрибутом [Export].
Примечание: **[PartCreationPolicy(CreationPolicy.NonShared)]** означает, что всякий раз, когда этот экземпляр запрошен, Mef создаст новый экземпляр, противоположность этому будет CreationPolicy.Shared, который создаст один экземпляр в первый раз и сохранит его, и будет обслуживать этот же экземпляр каждый раз, когда он запрошен (что является поведением, которого мы не хотим для контроллеров Mvc).
[sourcecode language=»csharp» highlight=»1,2″]
[Export]
[PartCreationPolicy(CreationPolicy.NonShared)]
public class HomeController : Controller
{
public ActionResult Index()
{
ViewBag.Message =
«Modify this template to jump-start your ASP.NET MVC application.»;
return View();
}
public ActionResult About()
{
ViewBag.Message = «Your app description page.»;
return View();
}
public ActionResult Contact()
{
ViewBag.Message = «Your contact page.»;
return View();
}
}
}
[/sourcecode]
Теперь для интересной части, это проект Mvc 4 из одной из моих предыдущих статей Multi-Step (Two-Factor) ASP.NET MVC 4 Registration with SMS using Twilio Cloud Communication and SimpleMembershipProvider for Increased User Validity, где мы использовали TwilioRestClient из их Api для отправки SMS-сообщений для двухфакторной регистрации. Давайте посмотрим, как мы можем внедрить это теперь с Mef в нашем действии Register.
**До:**
[sourcecode language=»csharp» highlight=»25,26,27″]
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public ActionResult Register(RegisterModel model)
{
if (ModelState.IsValid)
{
// Attempt to register the user
try
{
var smsVerificationCode =
GenerateSimpleSmsVerificationCode();
WebSecurity.CreateUserAndAccount(
model.UserName,
model.Password,
new
{
model.Mobile,
IsSmsVerified = false,
SmsVerificationCode = smsVerificationCode
},
true);
var twilioRestClient = new TwilioRestClient(
ConfigurationManager.AppSettings.Get(«Twilio:AccoundSid»),
ConfigurationManager.AppSettings.Get(«Twilio:AuthToken»));
twilioRestClient.SendSmsMessage(
«+19722001298»,
model.Mobile,
string.Format(
«Your ASP.NET MVC 4 with Twilio » +
«registration verification code is: {0}»,
smsVerificationCode)
);
Session[«registrationModel»] = model;
return RedirectToAction(«SmsVerification», «Account», model);
}
catch (MembershipCreateUserException e)
{
ModelState.AddModelError(«», ErrorCodeToString(e.StatusCode));
}
}
// If we got this far, something failed, redisplay form
return View(model);
}
[/sourcecode]
**После:**
[sourcecode language=»csharp» highlight=»25″]
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public ActionResult Register(RegisterModel model)
{
if (ModelState.IsValid)
{
// Attempt to register the user
try
{
var smsVerificationCode =
GenerateSimpleSmsVerificationCode();
WebSecurity.CreateUserAndAccount(
model.UserName,
model.Password,
new
{
model.Mobile,
IsSmsVerified = false,
SmsVerificationCode = smsVerificationCode
},
true);
var twilioRestClient = ServiceLocator.Current.GetInstance
twilioRestClient.SendSmsMessage(
«+19722001298»,
model.Mobile,
string.Format(
«Your ASP.NET MVC 4 with Twilio » +
«registration verification code is: {0}»,
smsVerificationCode)
);
Session[«registrationModel»] = model;
return RedirectToAction(«SmsVerification», «Account», model);
}
catch (MembershipCreateUserException e)
{
ModelState.AddModelError(«», ErrorCodeToString(e.StatusCode));
}
}
// If we got this far, something failed, redisplay form
return View(model);
}
[/sourcecode]
Обратите внимание, что в строке 25 мы теперь используем **ServiceLocator**, чтобы разрешить зависимость в нашем действии Register для TwilioRestClient. Здесь Mef будет заниматься активацией и настройкой нашего экземпляра TwilioRestClient для нас.
Идеально мы бы хотели обернуть (Composition Pattern) TwilioRestClient, реализовав наш собственный интерфейс и внедрив интерфейс с нашим обернутым TwilioRestClient. Если время позволяет, я расскажу об этом в другой статье, пока мы просто будем веселиться с внедрением закрытых классов с помощью Mef.
Давайте посмотрим на внедрение TwilioRestClient с помощью ServiceLocator в действии, запустив приложение и зарегистрировавшись.
Чтобы это произошло, нам нужно создать **TwilioRestClientMefAdapter** (это вариация шаблона Adapter), этот класс был необходим, потому что TwilioRestClient не имеет конструктора без параметров, и эти параметры были нашими Twilio AccountSid и AuthToken, которые находились в нашем Web.config.
[sourcecode language=»csharp» highlight=»1,6,8,9,14″]
[PartCreationPolicy(CreationPolicy.NonShared)]
public class TwilioRestClientMefAdapter
{
private readonly TwilioRestClient _twilioRestClient;
[ImportingConstructor]
public TwilioRestClientMefAdapter(
[Import(«Twilio:AccoundSid»)] string accountSid,
[Import(«Twilio:AuthToken»)] string authToken)
{
_twilioRestClient = new TwilioRestClient(accountSid, authToken);
}
[Export]
public TwilioRestClient TwilioRestClient
{
get { return _twilioRestClient; }
}
}
[/sourcecode]
Давайте отладим процесс активации, просто чтобы понять, что происходит здесь.
Теперь, когда мы внедряем наш TwilioRestClient с помощью
[sourcecode language=»csharp»]
var twilioRestClient = ServiceLocator.Current.GetInstance
[/sourcecode]
то, что помечено для экспорта, это свойство с именем TwilioRestClient. Чтобы Mef мог экспортировать это свойство, он должен создать экземпляр класса, в котором оно находится, в нашем случае это класс TwilioRestClientMefAdapter. Конструктор, помеченный [ImportingConstructor], является тем, что Mef будет сканировать, чтобы активировать этот класс. В конструкторе мы также внедряем наш AccountSid и AuthToken из нашего Web.config appSettings, который был загружен ранее в наш CompositionContainer с помощью нашего MefNameValueCollectionExportProvider.cs класса, где мы передали ConfigurationManager.AppSettings в наш MefMvcConfig.ConfigureContainer метод. К счастью для нас, ConfigurationManager.AppSettings является NameValueCollection.
[sourcecode language=»csharp» highlight=»14,15″]
private static CompositionContainer ConfigureContainer(
ComposablePartCatalog composablePartCatalog)
{
var path = HostingEnvironment.MapPath(«~/bin»);
if (path == null) throw new Exception(«Unable to find the path»);
var aggregateCatalog = new AggregateCatalog(new DirectoryCatalog(path));
if (composablePartCatalog != null)
aggregateCatalog.Catalogs.Add(composablePartCatalog);
return new CompositionContainer(
aggregateCatalog,
new MefNameValueCollectionExportProvider(
ConfigurationManager.AppSettings));
}
[/sourcecode]