Добавление внедрения зависимостей в ваше приложение ASP.NET MVC Twilio с помощью MEF

Автор: | 20.07.2026

*Это гостевая публикация от 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]