2012-08-29 5 views
4

특정 위치에 놓인 "플러그인"으로 구현할 수있는 몇 가지 미리 정의 된 인터페이스를 제공하여 기존 앱에 일부 확장 성을 추가 할 것을 고려하고 있습니다. 플러그인의 업데이트와 배포가 빈번히 이루어지는 동안 애플리케이션의 핵심은 거의 업데이트되지 않습니다.어셈블리 및 버전 관리

// in core assembly (core.dll) 
public interface IReportProvider{ 
    string GenerateReport(); 
} 

// in other assembly(plugin.dll) 
public class AwesomeReport : IReportProvider { 
    string GenerateReport(){ ... } 
} 

두 프로젝트가 같은 빌드 프로세스의 일부이며, 핵심 응용 프로그램이 자체에 배포됩니다 및 플러그인은 이후 단계에서의 삭제됩니다 :이 같은 설정을 가진 그러니까 기본적으로

.

제 문제는 어셈블리 버전 관리와 시간 경과에 따른 해결입니다. core.dll v1이 배포되었고 플러그인에 드롭한다고 가정 해 봅시다. 이것은 plugin.dll이 core.dll v1을 참조하는 경우 훌륭하게 작동합니다. 그러나 plugin.dll이 core.dll의 최신 버전 (빌드의 일부로 v2라고 함)에 대해 컴파일 된 경우 core.dll v2를 참조하기 때문에 플러그인이로드되지 않지만 배포 된 버전에는 core.dll 만 있습니다. v1.

이것은 합리적이고 예상되는 동작이지만이 프로젝트가 설정되는 방식에 두 가지 문제점이 있습니다. 즉, 빌드를 다시 실행하고 새 플러그인을 삭제하여 플러그인의 개발/업데이트를 수행 할 수 없습니다 (이제는 최신 버전 종속성이 있음).

(예전 어셈블리에 새로운 어셈블리를 해석 할 때 발생할 수있는 잠재적 문제와 형식 정의의 잠재적 불일치에 대해 알고 있습니다. 문제는 유형 정의가 일치하지 않는 문제를 해결하는 것이 아니라 고급 어셈블리 해결 문제를 해결하는 데 있습니다.

  1. 하는 추가 바인딩 리디렉션, Web.config의 모든 core.dll 지시 :. 내가 정말 좋아하는 것처럼)

    나는 간단하다 어느 것도 작동 물건을 얻기위한 몇 가지 옵션을 참조 core.dll v1 참조로 해결할 v1 + 참조

  2. 인터페이스 정의가 포함 된 'contracts.dll'어셈블리 만들기 및이 특정 어셈블리의 버전 번호를 빌드간에 변경하지 않음
  3. 배포 된 버전의 core.dll에 대한 플러그인 작성 (어떤 방식 으로든 개발 된 버전을 참조)

언급했듯이, 이들 중 아무 것도 나를 위해 정말로 낮은 교수형 과일이 아니며 누군가가 더 나은 해결책을 갖고 있기를 바라고 있습니까?

+0

버전 할 마법 솔루션 및 DLL 지옥의이 종류가 없습니다. CLR은 이미 탁월한 보호 기능을 가지고 있으며,이를 피하는 것이 마지막으로 고려해야 할 사항입니다. 대신 단단한 배포 시나리오에 집중하십시오. –

+1

"Contracts.dll"이 단순하지 않은 이유는 무엇입니까? 나는 이것이 좋은 해결책이라고 생각한다. 계약 라이브러리는 절대로 변경되지 않고 core.dll과 plugin.dll은이를 통신용 "미들웨어"로 사용합니다. –

답변

0

당신의 이유가 무엇이든 3 점을 피할 수 있다면 (나는 더 믿을 만하다고 생각합니다) Assembly Resolve Event을 이용할 수 있습니다.

은 당신의 플러그인을로드하기 전에 당신의 core.dll에

static Assembly AppDomain_AssemblyResolve(object sender, ResolveEventArgs args) 
     { 
      //replace the if check with more reliable check such as matching the public key as well 
      if (args.Name.Contains(Assembly.GetExecutingAssembly().GetName().Name)) 
      { 
       Console.WriteLine("Resolved " + args.Name + " as " + Assembly.GetExecutingAssembly().GetName()); 
       return Assembly.GetExecutingAssembly(); 
      } 

      return null; 
     } 

바인딩 위의 핸들러를 다음과 같은 코드를 포함합니다. 현재 Appdomain에 플러그인을로드 한 다음 현재 도메인의 AssemblyResolve에 바인드하십시오.

예 :

[SecurityPermission(SecurityAction.Demand, ControlAppDomain = true)] 
public static void LoadPlugins() 
{ 
    AppDomain.CurrentDomain.AssemblyResolve += AppDomain_AssemblyResolve; 
    Assembly pluginAssembly = AppDomain.CurrentDomain.Load("MyPlugin"); 
} 
3

나는이 공간에서 아주 광범위하게 일했으며, 범위의 안팎을 정확히 경계해야한다는 것을 일관되게 발견했다. 가장 큰 확장 성을 원할 경우 위험 할 수 있지만 비용은 모두 발생합니다. 그 비용은 컴파일 타임, 컨벤션, 베스트 프랙티스, 아마도 자동 빌드 체크의 형태로 이루어 지거나 복잡한 런타임이 될 것입니다.

는 나열했습니다 옵션을 해결하기 위해 :

  1. 바인딩 리디렉션 솔루션을 달성하기 위해 단지 부분적인 도구입니다. 그것들은 프로그램이 다른 DLL 대신에 DLL의 한 버전을 '미끄러지게 (slip)'하도록 허용 할 것이지만, 방법이 바뀌면 발생하는 문제를 마술처럼 풀지는 못할 것입니다. MissingMethodException? 여기에도 무언가가 없어도 의존성 체인이 있습니다. dependency chains 응용 프로그램이 종속성 'A'를 버전 1 객체로 처리하는 동안 내부적으로 응용 프로그램으로 전달되어 v1.0으로 캐스팅되는 이후 버전에서 무언가를 생성하여 예외가 발생하는 것을 볼 수 있습니다. 이것은 다루기 힘들 수 있으며 위험 요소 중 하나 일뿐입니다.

  2. 계약 어셈블리 버전을 빌드간에 동일하게 유지 이것은 우아하게 작동하지만 빌드 시간에서 런타임으로 복잡성을 지연시키는 것입니다. 변경 사항이 버전간에 호환성을 파괴하지 않도록하려면 부지런히해야합니다. 당신의 애플리케이션이 오래 될수록 더 이상 사용하지 않을 계약서에 많은 선언을 수집 할 것입니다. 결국이 파일은 크고, 성 가시고, 개발자들에게 혼란스러워 질 것입니다. 그리고 그것은 여러분이 가지고있는 모든 엔티티 계약을 고려하지도 않습니다!

  3. 나는이 내용이 의미하는 바가 무엇인지, 그리고 문제 공간을 어떻게 다루는 지 잘 모르겠습니다.

'SDK'의 각 주요 릴리스에 대해 새로운 계약을 만드는 것이 좋습니다. 이것은 상당한 기간 동안 주요 기능을 수정한다는 점에서 정치적으로 유리한 점이 있습니다. 즉, 기능 요청을 합리적인 수준으로 유지할 수 있다는 의미이며, 새로운 계약을 요구하는 그 이상의 모든 사항은 '다음 주요 릴리스 '. 그러나 이것은 당신의 기능 설계에 부지런함을 필요로합니다 - 당신이 가장 분명한 요구 사항을 선점 할 수 있도록 미리 생각한 계약을 작성하십시오 - 그러나 나는 이것이 어쨌든 말하지 않고 있다는 것을 정말로 느낍니다 ... 그들은 계약 ' 이런 이유로. 새로운 계약은 각각 버전 네임 스페이스 (Company.Product.Contracts.v1_0, Company.Product.Contracts.v1_1)에 존재합니다.

저는 계약서를 체인화하지 않을 것입니다 (마지막으로 상속 한 계약서가 나올 때마다). 그렇게하면 버전 번호를 동일하게 유지하면서 문제를 해결할 수 있습니다. 체인을 완전히 끊지 않고 기능을 완전히 제거 할 수는 없습니다.

플러그인이로드되면 호스트에 지원되는 기능 수준 (계약 버전)을 요청할 수 있습니다. 이전 호스트/최신 플러그인 시나리오의 경우 : 플러그인을 프로그래밍하여 플러그인이 런타임 기능을 줄이고 낮은 호스트 기능을 사용하거나 단순히로드를 거부 할 수 있습니다. 플러그인은 단순히 존재하지 않는 호스트의 기능을 활용할 수있는 마법이 없으므로 어쨌든 이러한 검사를 수행해야합니다. Microsoft의 MAF 프레임 워크는 shim 인프라를 사용하여이를 달성하려고 시도하지만 대부분의 사람들에게는 엄청난 복잡성을 초래합니다.

그래서, 당신이 고려해야 할 몇 가지 사항은 다음과 같습니다

  1. 범위 당신의 확장 요구 사항! 당신이 달성하고자하는 모든 것은 지속적인 유지 보수 비용이들 것입니다. 그들은 종종 주위에 훨씬 더 전달하기 때문에
  2. 당신이 당신의 계약 로직 계약뿐만 아니라 개체를 포함하는 것을 잊지 마세요 기능
  3. 을 비하 대해 이동하는 방법에 대해 생각, 이러한 논리 계약에 약간 다른 고려 사항이있다.
  4. 각 호환성 검사가 런타임 대신 컴파일 타임에 더 잘 수행되는지 (또는 그 반대) 신중하게 고려하십시오.
  5. 버전 번호 매기기! 어셈블리 버전 번호는 런타임 동작에 매우 유용합니다. 파일 버전 번호는 버전 관리에서 소스로 DLL을 추적하는 데 도움이됩니다. 두 버전 모두 사용하십시오!
  6. 사용자 지정 DLL 확인을 수행하는 경우 app.config를 사용하여 어셈블리 해결 이벤트가 아닌 사용자 지정 DLL 위치를 정의합니다. config 접근법은 훨씬 더 예측 가능하며, 쉽게 읽을 수있는 XML로 선언되었습니다! 퓨전 로그 뷰어는 DLL이 삽입 된 곳에서 프로빙 체인이 어디에 있는지 잘보고합니다. 반면 어셈블리 - 해결 이벤트는 코드에서 모든 논리와 규칙을 숨 깁니다. 유일한 단점은 app.config를 사용하면 응용 프로그램이 구성 파일 (일반적으로 응용 프로그램 다시 시작)을 다시 읽을 때까지 변경 사항이 적용되지 않는다는 것입니다. 그러나 플러그인의 AppDomain 격리를 수행하면이 문제를 해결할 수도 있습니다.

'core.dll'문제에 ... 내 생각에, 이것은 비교적 간단한 문제입니다. 핵심 계약 DLL의 모든 내용은 버전 네임 스페이스 (위의 Company.Product.v1_0 등 참조) 아래에 있어야하므로 실제로 DLL에 버전 번호도 포함되어 있습니다. 이렇게하면 bin 폴더에 배포 할 때 DLL 덮어 쓰기 문제가 제거됩니다. GAC에 의존하지 마십시오. 장기적으로는 고통이 될 것입니다. GAC가 모든 것을 오버라이드한다는 것은 개발자가 항상 잊는 것처럼 보이며 이는 디버깅의 악몽이 될 수 있습니다. 또한 권한과 관련하여 배포 시나리오에도 영향을 미칩니다.

DLL 이름을 동일하게 유지해야하는 경우 - 응용 프로그램에서 DLL을 서로 위에 겹쳐 쓰지 않도록 저장할 수있는 '로컬 gac'을 만들 수 있지만 모두 런타임에서 해결할 수 있습니다. app.config (see my answer here)에서 'binding redirect'를 확인하십시오. 이것은 응용 프로그램의 bin 폴더 아래 'pseudo GAC 폴더 구조'와 결합 할 수 있습니다. 그러면 앱이 맞춤 어셈블리 해석 코드 로직없이 필요한 모든 버전의 DLL을 찾을 수 있습니다. 응용 프로그램과 함께 이전에 지원되는 core.dll 버전을 모두 배포 할 것입니다. 응용 프로그램이 버전 9에 도달하고 플러그인 버전 7과 8을 지원하기로 결정한 경우 Core.7.dll, Core.8.dll 및 Core.9.dll 만 포함하면됩니다. 플러그인로드 논리에서 이전 버전의 Core에 대한 종속성을 감지하고 사용자에게 플러그인이 호환되지 않는다고 경고해야합니다.

, 당신의 원인에 타당한 다른 것을 생각하면, 나는 다시 확인하겠습니다이 주제에 많이 있습니다

...

+0

좋은 그림. 일부 태블릿 PC? – Wernight

+0

Hahaha - no, Wacom 타블렛 - 우리는 문서에 대한 빠른 처리 요구가 있기 때문에 PowerPoint 등에서 시간을 낭비하지 않아도됩니다. 빨리 끝내십시오. 그리고 붙어 있으면 훨씬 더 효율적입니다. – Adam