2017-02-21 14 views
2

Я разрабатываю API MVC в отдельной библиотеке классов. Методы API используют маршрутизацию атрибутов. API будет использоваться другими приложениями MVC (не построенными мной).Динамический префикс маршрута для контроллеров в отдельной библиотеке

Основное приложение MVC будет ссылаться на мою сборку библиотеки и называть AddMvc()/UseMvc() в своем собственном классе запуска. Он сможет динамически устанавливать URL-адрес корневого API-интерфейса для моей библиотеки API (из делегата настройки конфигурации или параметров), чтобы он мог убедиться, что нет конфликтов с его собственными маршрутами, которые могут использовать либо маршрутизацию атрибутов, либо централизованную маршрутизацию.

Итак, скажем, моя библиотека API имеет маршрут product/{id}. Основное приложение должно иметь возможность выбрать любой префикс маршрута, например api/product/{id} или some/other/prefix/product/{id}.

При запуске MVC обнаружит все контроллеры/маршруты во всех ссылочных сборках, а также обнаружит и зарегистрирует маршруты моей библиотеки API, но только на жестко закодированном маршруте product/{id} без какого-либо префикса.

Я пытался получить MVC для регистрации маршрутов с помощью префикса, но пока ничего не добился. Основное приложение будет вызывать пользовательские методы конфигурации AddMyApi()/UseMyApi(), поэтому я могу выполнить настройку/настройку для своей библиотеки. Некоторые из вещей, которые я пробовал:

Mapping

app.Map("/custom-prefix", api => 
{ 
    api.UseMvc(); 
}); 

Это приведет к дублированию маршрутов как для custom-prefix/product/{id} и product/{id}.

Конвенция Маршрут

основе http://www.strathweb.com/2016/06/global-route-prefix-with-asp-net-core-mvc-revisited/

services.AddMvc(options => 
{ 
    options.Conventions.Insert(0, new RouteConvention(new RouteAttribute("custom-prefix"))); 
}); 

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

Пользовательского маршрут атрибут

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

Откладывать открытие маршрутов

Основываясь на http://www.strathweb.com/2015/04/asp-net-mvc-6-discovers-controllers/

Я добавил [NonController] к контроллерам библиотеки, чтобы предотвратить их обнаружили при запуске основного приложения. Однако я не смог добавить их позже, и я полагаю, что я столкнусь с той же проблемой, что и основное приложение, перезаписывающее параметры MVC.

Области

Я не могу использовать области, поскольку основное приложение может решить запустить API из корня (без префикса).

Так что я застрял в том, как решить эту проблему. Любая помощь приветствуется.

ответ

1

Я считаю, что соглашение является правильным подходом здесь, и бит, который вам не хватает, просто предоставляет правильный метод расширения для регистрации вашей библиотеки в MVC.

Начните с создания соглашения, которое добавит префикс для всех контроллеров, которые передают определенный селектор.

  • Основано на one I wrote для добавления префиксов культуры, но идея очень похожа на статью, которую вы связали.
  • В принципе, он либо обновит существующие существующие AttributeRouteModel, либо добавит новый, если ни один не найден.

Это было бы примером такой конвенции:

public class ApiPrefixConvention: IApplicationModelConvention 
{ 
    private readonly string prefix; 
    private readonly Func<ControllerModel, bool> controllerSelector; 
    private readonly AttributeRouteModel onlyPrefixRoute; 
    private readonly AttributeRouteModel fullRoute; 

    public ApiPrefixConvention(string prefix, Func<ControllerModel, bool> controllerSelector) 
    { 
     this.prefix = prefix; 
     this.controllerSelector = controllerSelector;    

     // Prepare AttributeRouteModel local instances, ready to be added to the controllers 

     // This one is meant to be combined with existing route attributes 
     onlyPrefixRoute = new AttributeRouteModel(new RouteAttribute(prefix)); 

     // This one is meant to be added as the route for api controllers that do not specify any route attribute 
     fullRoute = new AttributeRouteModel(
      new RouteAttribute("api/[controller]")); 
    } 

    public void Apply(ApplicationModel application) 
    { 
     // Loop through any controller matching our selector 
     foreach (var controller in application.Controllers.Where(controllerSelector)) 
     { 
      // Either update existing route attributes or add a new one 
      if (controller.Selectors.Any(x => x.AttributeRouteModel != null)) 
      { 
       AddPrefixesToExistingRoutes(controller); 
      } 
      else 
      { 
       AddNewRoute(controller); 
      } 
     } 
    }   

    private void AddPrefixesToExistingRoutes(ControllerModel controller) 
    { 
     foreach (var selectorModel in controller.Selectors.Where(x => x.AttributeRouteModel != null).ToList()) 
     { 
      // Merge existing route models with the api prefix 
      var originalAttributeRoute = selectorModel.AttributeRouteModel;     
      selectorModel.AttributeRouteModel = 
       AttributeRouteModel.CombineAttributeRouteModel(onlyPrefixRoute, originalAttributeRoute); 
     } 
    } 

    private void AddNewRoute(ControllerModel controller) 
    { 
     // The controller has no route attributes, lets add a default api convention 
     var defaultSelector = controller.Selectors.First(s => s.AttributeRouteModel == null); 
     defaultSelector.AttributeRouteModel = fullRoute; 
    } 
} 

Теперь, если все это было частью приложения вы пишете вместо библиотеки, вы бы просто зарегистрировать его как:

services.AddMvc(opts => 
{ 
    var prefixConvention = new ApiPrefixConvention("api/", (c) => c.ControllerType.Namespace == "WebApplication2.Controllers.Api"); 
    opts.Conventions.Insert(0, prefixConvention); 
}); 

Однако, поскольку вы предоставляете библиотеку, вы должны предоставить метод расширения, например AddMyLibrary("some/prefix"), который позаботится о добавлении этого соглашения и любых других настройках, таких как регистрация необходимых сервисов.

Таким образом, вы можете написать метод расширения для IMvcBuilder и обновить MvcOptions внутри этого метода. Хорошая вещь, что, поскольку является продолжением IMvcBuilder, он всегда будет вызываться после того, как по умолчанию AddMvc():

public static IMvcBuilder AddMyLibrary(this IMvcBuilder builder, string prefix = "api/") 
{ 
    // instantiate the convention with the right selector for your library. 
    // Check for namespace, marker attribute, name pattern, whatever your prefer 
    var prefixConvention = new ApiPrefixConvention(prefix, (c) => c.ControllerType.Namespace == "WebApplication2.Controllers.Api"); 

    // Insert the convention within the MVC options 
    builder.Services.Configure<MvcOptions>(opts => opts.Conventions.Insert(0, prefixConvention)); 

    // perform any extra setup required by your library, like registering services 

    // return builder so it can be chained 
    return builder; 
} 

Тогда вы бы попросить пользователей вашей библиотеки, чтобы включить его в своем приложении, как в:

services.AddMvc().AddMyLibrary("my/api/prefix/"); 
+0

Спасибо, я смог использовать ваше решение. Вместо метода AddMyLibrary() я устанавливаю условное обозначение префикса в производном классе IConfigureOptions , поэтому мне не нужно будет просить пользователя добавить services.AddMvc(). AddMyLibrary(). Единственный способ, которым пользователь может сломать это, если он впоследствии очистит коллекцию конвенций. – Carvellis

Смежные вопросы