Refactoring, navigation and lifetime of mef

Extract interfaces of mef exports to separate dll
to decouple things a bit.
Only load reactive things when requesting UI dialogs,
TeamExplorer items should not load any reactive stuff.
Introduce ExportFactory to dispose of IDisposable mef
components properly.
Start fleshing out team explorer implementation.
This commit is contained in:
Andreia Gaita 2015-01-27 15:42:09 +01:00
Родитель 6b235e8327
Коммит a107c79d66
26 изменённых файлов: 628 добавлений и 221 удалений

Просмотреть файл

@ -45,6 +45,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Build", "Build", "{7B6C5F8D
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{8A7DA2E7-262B-4581-807A-1C45CE79CDFD}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GitHub.Exports", "src\GitHub.Exports\GitHub.Exports.csproj", "{9AEA02DB-02B5-409C-B0CA-115D05331A6B}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -83,6 +85,10 @@ Global
{4A84E568-CA86-4510-8CD0-90D3EF9B65F9}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4A84E568-CA86-4510-8CD0-90D3EF9B65F9}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4A84E568-CA86-4510-8CD0-90D3EF9B65F9}.Release|Any CPU.Build.0 = Release|Any CPU
{9AEA02DB-02B5-409C-B0CA-115D05331A6B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{9AEA02DB-02B5-409C-B0CA-115D05331A6B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{9AEA02DB-02B5-409C-B0CA-115D05331A6B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{9AEA02DB-02B5-409C-B0CA-115D05331A6B}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE

Просмотреть файл

@ -1,11 +1,7 @@
namespace GitHub.Authentication
using GitHub.Exports;
namespace GitHub.Authentication
{
public enum AuthenticationResult
{
CredentialFailure,
VerificationFailure,
Success
}
public static class AuthenticationResultExtensions
{

Просмотреть файл

@ -163,6 +163,10 @@
<None Include="packages.config" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\GitHub.Exports\GitHub.Exports.csproj">
<Project>{9aea02db-02b5-409c-b0ca-115d05331a6b}</Project>
<Name>GitHub.Exports</Name>
</ProjectReference>
<ProjectReference Include="..\GitHub.Extensions.Reactive\GitHub.Extensions.Reactive.csproj">
<Project>{6559e128-8b40-49a5-85a8-05565ed0c7e3}</Project>
<Name>GitHub.Extensions.Reactive</Name>

Просмотреть файл

@ -7,6 +7,7 @@ using Akavache;
using GitHub.Authentication;
using NullGuard;
using ReactiveUI;
using GitHub.Exports;
namespace GitHub.Models
{

Просмотреть файл

@ -3,6 +3,7 @@ using System.Reactive;
using GitHub.Authentication;
using GitHub.Helpers;
using ReactiveUI;
using GitHub.Exports;
namespace GitHub.Models
{

Просмотреть файл

@ -1,6 +1,7 @@
using System;
using GitHub.Authentication;
using ReactiveUI;
using GitHub.Exports;
namespace GitHub.Models
{

Просмотреть файл

@ -3,6 +3,7 @@ using System.Reactive;
using System.Reactive.Linq;
using GitHub.Authentication;
using ReactiveUI;
using GitHub.Exports;
namespace GitHub.Models
{

Просмотреть файл

@ -15,6 +15,7 @@ using NLog;
using Octokit;
using ReactiveUI;
using Authorization = Octokit.Authorization;
using GitHub.Exports;
namespace GitHub.Models
{

Просмотреть файл

@ -7,6 +7,7 @@ using Akavache;
using GitHub.Authentication;
using GitHub.Extensions.Reactive;
using ReactiveUI;
using GitHub.Exports;
namespace GitHub.Models
{

Просмотреть файл

@ -12,32 +12,142 @@ using GitHub.Info;
using GitHub.Models;
using GitHub.Services;
using GitHub.Validation;
using GitHub.Exports;
using NullGuard;
using ReactiveUI;
using System.Windows.Input;
namespace GitHub.ViewModels
{
[SuppressMessage("Microsoft.Design", "CA1001:TypesThatOwnDisposableFieldsShouldBeDisposable")]
[Export(typeof(LoginControlViewModel))]
[Export(typeof(ILoginDialog))]
[PartCreationPolicy(CreationPolicy.NonShared)]
public class LoginControlViewModel : ReactiveValidatableObject, IDisposable
public class LoginControlViewModel : ReactiveValidatableObject, ILoginDialog, IDisposable
{
string enterpriseUrl;
readonly ObservableAsPropertyHelper<bool> isLoggingInToEnterprise;
readonly ObservableAsPropertyHelper<bool> isLoginInProgress;
readonly Subject<AuthenticationResult> authenticationResults;
readonly ObservableAsPropertyHelper<string> loginButtonText;
bool loginFailed;
string loginFailedText;
readonly ObservableAsPropertyHelper<LoginMode> loginMode;
readonly ObservableAsPropertyHelper<LoginTarget> loginTarget;
readonly ObservableAsPropertyHelper<VisualState> visualState;
string password;
string usernameOrEmail;
Uri enterpriseHostBaseUrl;
readonly Lazy<IEnterpriseProbe> lazyEnterpriseProbe;
const string notEnterpriseServerError = "Not an Enterprise server. Please enter an Enterprise URL";
public ReactiveCommand<AuthenticationResult> LoginCommand { get; private set; }
public ICommand LoginCmd { get { return LoginCommand; } }
public ReactiveCommand<object> CancelCommand { get; private set; }
public ICommand CancelCmd { get { return CancelCommand; } }
public IObservable<object> CancelEvt { get { return CancelCommand; } }
public ReactiveCommand<object> ForgotPasswordCommand { get; private set; }
public ReactiveCommand<object> ShowDotComLoginCommand { get; set; }
public ReactiveCommand<object> ShowEnterpriseLoginCommand { get; set; }
public ReactiveCommand<object> SignupCommand { get; private set; }
public ReactiveCommand<object> LearnMoreCommand { get; private set; }
string enterpriseUrl;
[ValidateIf("IsLoggingInToEnterprise")]
[Required(ErrorMessage = "Please enter an Enterprise URL")]
[AllowNull]
public string EnterpriseUrl
{
get { return enterpriseUrl; }
set { this.RaiseAndSetIfChanged(ref enterpriseUrl, value); }
}
readonly ObservableAsPropertyHelper<bool> isLoggingInToEnterprise;
public bool IsLoggingInToEnterprise
{
get { return isLoggingInToEnterprise.Value; }
}
readonly ObservableAsPropertyHelper<bool> isLoginInProgress;
public bool IsLoginInProgress
{
get { return isLoginInProgress.Value; }
}
readonly ObservableAsPropertyHelper<string> loginButtonText;
public string LoginButtonText
{
get { return loginButtonText.Value; }
}
bool loginFailed;
public bool LoginFailed
{
get { return loginFailed; }
set { this.RaiseAndSetIfChanged(ref loginFailed, value); }
}
string loginFailedText;
public string LoginFailedText
{
get { return loginFailedText; }
private set { this.RaiseAndSetIfChanged(ref loginFailedText, value); }
}
readonly ObservableAsPropertyHelper<LoginMode> loginMode;
public LoginMode LoginMode
{
get { return loginMode.Value; }
}
public string LoginPrefix { get; set; }
readonly ObservableAsPropertyHelper<LoginTarget> loginTarget;
public LoginTarget LoginTarget
{
get { return loginTarget.Value; }
}
readonly ObservableAsPropertyHelper<VisualState> visualState;
public VisualState VisualState
{
get { return visualState.Value; }
}
string password;
[AllowNull]
public string Password
{
[return: AllowNull]
get
{ return password; }
set { this.RaiseAndSetIfChanged(ref password, value); }
}
readonly ObservableAsPropertyHelper<Uri> forgotPasswordUrl;
public Uri ForgotPasswordUrl
{
get { return forgotPasswordUrl.Value; }
}
Uri enterpriseHostBaseUrl;
Uri EnterpriseHostBaseUrl
{
get { return enterpriseHostBaseUrl; }
set { this.RaiseAndSetIfChanged(ref enterpriseHostBaseUrl, value); }
}
// HACKETY HACK!
// Because #Bind() doesn't yet set up validation, we must use XAML bindings for username and password.
// But, because our SecurePasswordBox manipulates base.Text, it doesn't work with XAML binding.
// (It binds the password mask, not the password.)
// So, this property is a "black hole" to point the XAML binding to so validation works.
// And the actual password is bound to #Password via #Bind(). Ugly? Yep.
[Required(ErrorMessage = "Please enter your password")]
public string PasswordNoOp { get; set; }
protected IRepositoryHosts RepositoryHosts { get; private set; }
string usernameOrEmail;
[Required(ErrorMessage = "Please enter your username or email address")]
[AllowNull]
public string UsernameOrEmail
{
[return: AllowNull]
get
{ return usernameOrEmail; }
set { this.RaiseAndSetIfChanged(ref usernameOrEmail, value); }
}
readonly Subject<AuthenticationResult> authenticationResults;
public IObservable<AuthenticationResult> AuthenticationResults { get { return authenticationResults; } }
[SuppressMessage("Microsoft.Maintainability", "CA1505:AvoidUnmaintainableCode", Justification = "It's Rx baby")]
[ImportingConstructor]
@ -141,113 +251,6 @@ namespace GitHub.ViewModels
ForgotPasswordCommand.Subscribe(_ => browser.OpenUrl(ForgotPasswordUrl));
}
public ReactiveCommand<object> CancelCommand { get; private set; }
[ValidateIf("IsLoggingInToEnterprise")]
[Required(ErrorMessage = "Please enter an Enterprise URL")]
[AllowNull]
public string EnterpriseUrl
{
get { return enterpriseUrl; }
set { this.RaiseAndSetIfChanged(ref enterpriseUrl, value); }
}
public ReactiveCommand<object> ForgotPasswordCommand { get; private set; }
public bool IsLoggingInToEnterprise
{
get { return isLoggingInToEnterprise.Value; }
}
public bool IsLoginInProgress
{
get { return isLoginInProgress.Value; }
}
public IObservable<AuthenticationResult> AuthenticationResults { get { return authenticationResults; } }
public string LoginButtonText
{
get { return loginButtonText.Value; }
}
public ReactiveCommand<AuthenticationResult> LoginCommand { get; private set; }
public bool LoginFailed
{
get { return loginFailed; }
set { this.RaiseAndSetIfChanged(ref loginFailed, value); }
}
public string LoginFailedText
{
get { return loginFailedText; }
private set { this.RaiseAndSetIfChanged(ref loginFailedText, value); }
}
public LoginMode LoginMode
{
get { return loginMode.Value; }
}
public string LoginPrefix { get; set; }
public LoginTarget LoginTarget
{
get { return loginTarget.Value; }
}
public VisualState VisualState
{
get { return visualState.Value; }
}
[AllowNull]
public string Password
{
[return: AllowNull]
get { return password; }
set { this.RaiseAndSetIfChanged(ref password, value); }
}
public Uri ForgotPasswordUrl
{
get { return forgotPasswordUrl.Value; }
}
Uri EnterpriseHostBaseUrl
{
get { return enterpriseHostBaseUrl; }
set { this.RaiseAndSetIfChanged(ref enterpriseHostBaseUrl, value); }
}
// HACKETY HACK!
// Because #Bind() doesn't yet set up validation, we must use XAML bindings for username and password.
// But, because our SecurePasswordBox manipulates base.Text, it doesn't work with XAML binding.
// (It binds the password mask, not the password.)
// So, this property is a "black hole" to point the XAML binding to so validation works.
// And the actual password is bound to #Password via #Bind(). Ugly? Yep.
[Required(ErrorMessage = "Please enter your password")]
public string PasswordNoOp { get; set; }
protected IRepositoryHosts RepositoryHosts { get; private set; }
public ReactiveCommand<object> ShowDotComLoginCommand { get; set; }
public ReactiveCommand<object> ShowEnterpriseLoginCommand { get; set; }
public ReactiveCommand<object> SignupCommand { get; private set; }
public ReactiveCommand<object> LearnMoreCommand { get; private set; }
[Required(ErrorMessage = "Please enter your username or email address")]
[AllowNull]
public string UsernameOrEmail
{
[return: AllowNull]
get { return usernameOrEmail; }
set { this.RaiseAndSetIfChanged(ref usernameOrEmail, value); }
}
public void Dispose()
{

Просмотреть файл

@ -8,12 +8,13 @@ using GitHub.Validation;
using NullGuard;
using Octokit;
using ReactiveUI;
using GitHub.Exports;
namespace GitHub.ViewModels
{
[Export(typeof(TwoFactorDialogViewModel))]
[Export(typeof(ITwoFactorDialog))]
[PartCreationPolicy(CreationPolicy.Shared)]
public class TwoFactorDialogViewModel : ReactiveValidatableObject
public class TwoFactorDialogViewModel : ReactiveValidatableObject, ITwoFactorDialog
{
bool isAuthenticationCodeSent;
string authenticationCode;

Просмотреть файл

@ -0,0 +1,15 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace GitHub.Exports
{
public enum AuthenticationResult
{
CredentialFailure,
VerificationFailure,
Success
}
}

Просмотреть файл

@ -0,0 +1,57 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{9AEA02DB-02B5-409C-B0CA-115D05331A6B}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>GitHub.Exports</RootNamespace>
<AssemblyName>GitHub.Exports</AssemblyName>
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.ComponentModel.Composition" />
<Reference Include="System.Core" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="AuthenticationResult.cs" />
<Compile Include="ICloneDialog.cs" />
<Compile Include="ILoginDialog.cs" />
<Compile Include="ITwoFactorDialog.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>

Просмотреть файл

@ -0,0 +1,12 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace GitHub.Exports
{
public interface ICloneDialog
{
}
}

Просмотреть файл

@ -0,0 +1,15 @@
using System;
using System.Windows.Input;
namespace GitHub.Exports
{
public interface ILoginDialog
{
string UsernameOrEmail { get; set; }
string Password { get; set; }
ICommand LoginCmd { get; }
ICommand CancelCmd { get; }
IObservable<object> CancelEvt { get; }
IObservable<AuthenticationResult> AuthenticationResults { get; }
}
}

Просмотреть файл

@ -0,0 +1,12 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace GitHub.Exports
{
public interface ITwoFactorDialog
{
}
}

Просмотреть файл

@ -0,0 +1,36 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("GitHub.Exports")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("GitHub.Exports")]
[assembly: AssemblyCopyright("Copyright © 2015")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("9aea02db-02b5-409c-b0ca-115d05331a6b")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]

Просмотреть файл

@ -0,0 +1,75 @@
using GitHub.VisualStudio.Services;
using Microsoft.VisualStudio.ComponentModelHost;
using Microsoft.VisualStudio.Shell;
using System;
using System.Collections.Generic;
using System.ComponentModel.Design;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace GitHub.VisualStudio
{
public abstract class PackageBase : Package
{
IServiceProvider serviceProvider;
protected IServiceProvider ServiceProvider
{
get { return serviceProvider; }
set
{
serviceProvider = value;
}
}
public PackageBase()
{
ServiceProvider = this;
}
public PackageBase(IServiceProvider serviceProvider)
{
ServiceProvider = serviceProvider;
}
protected void AddTopLevelMenuItem(
uint packageCommandId,
EventHandler eventHandler)
{
var menuCommandService = GetService(typeof(IMenuCommandService)) as IMenuCommandService;
var menuCommandId = new CommandID(GuidList.guidGitHubCmdSet, (int)packageCommandId);
var menuItem = new MenuCommand(eventHandler, menuCommandId);
menuCommandService.AddCommand(menuItem);
}
public T GetService<T>()
{
Debug.Assert(this.serviceProvider != null, "GetService<T> called before service provider is set");
if (serviceProvider == null)
return default(T);
return (T)serviceProvider.GetService(typeof(T));
}
public Ret GetService<T, Ret>() where Ret : class
{
return GetService<T>() as Ret;
}
public T GetExportedValue<T>()
{
var componentModel = (IComponentModel)GetService<SComponentModel>();
if (componentModel == null)
return default(T);
var exportProvider = componentModel.DefaultExportProvider;
return exportProvider.GetExportedValue<T>();
}
protected void EnsureUIProvider()
{
var ui = GetExportedValue<UIProvider>();
ui.EnsureProvider(GetService<SComponentModel, IComponentModel>().DefaultExportProvider);
}
}
}

Просмотреть файл

@ -0,0 +1,95 @@
using Microsoft.TeamFoundation.Client;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace GitHub.VisualStudio.Base
{
public abstract class TeamExplorerBase : IDisposable, INotifyPropertyChanged
{
bool subscribed = false;
IServiceProvider serviceProvider;
protected IServiceProvider ServiceProvider
{
get { return serviceProvider; }
set
{
if (serviceProvider != null)
UnsubscribeContextChanges();
serviceProvider = value;
if (serviceProvider != null)
SubscribeContextChanges();
}
}
protected ITeamFoundationContext CurrentContext
{
get
{
var manager = GetService<ITeamFoundationContextManager>();
if (manager != null)
return manager.CurrentContext;
return null;
}
}
void SubscribeContextChanges()
{
Debug.Assert(serviceProvider != null, "ServiceProvider must be set before subscribing to context changes");
if (serviceProvider == null || subscribed)
return;
var manager = GetService<ITeamFoundationContextManager>();
if (manager != null)
{
manager.ContextChanged += ContextChanged;
subscribed = true;
}
}
void UnsubscribeContextChanges()
{
var manager = GetService<ITeamFoundationContextManager>();
if (manager != null)
{
manager.ContextChanged -= ContextChanged;
subscribed = false;
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
public virtual void Dispose()
{
UnsubscribeContextChanges();
}
public T GetService<T>()
{
Debug.Assert(this.serviceProvider != null, "GetService<T> called before service provider is set");
if (serviceProvider == null)
return default(T);
return (T)serviceProvider.GetService(typeof(T));
}
public Ret GetService<T, Ret>() where Ret : class
{
return GetService<T>() as Ret;
}
protected virtual void ContextChanged(object sender, ContextChangedEventArgs e)
{
}
}
}

Просмотреть файл

@ -33,9 +33,10 @@
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<RunCodeAnalysis>true</RunCodeAnalysis>
<RunCodeAnalysis>false</RunCodeAnalysis>
<CodeAnalysisRuleSet>..\..\script\GitHubVS.ruleset</CodeAnalysisRuleSet>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<TreatWarningsAsErrors>false</TreatWarningsAsErrors>
<CodeAnalysisIgnoreGeneratedCode>false</CodeAnalysisIgnoreGeneratedCode>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
@ -52,6 +53,30 @@
<HintPath>..\..\packages\EditorUtils2013.1.4.1.1\lib\net40\EditorUtils2013.dll</HintPath>
</Reference>
<Reference Include="Microsoft.CSharp" />
<Reference Include="Microsoft.TeamFoundation.Client, Version=12.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\..\..\..\..\..\Program Files (x86)\Microsoft Visual Studio 14.0\Common7\IDE\CommonExtensions\Microsoft\TeamFoundation\Team Explorer\Microsoft.TeamFoundation.Client.dll</HintPath>
</Reference>
<Reference Include="Microsoft.TeamFoundation.Common, Version=12.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\..\..\..\..\..\Program Files (x86)\Microsoft Visual Studio 14.0\Common7\IDE\CommonExtensions\Microsoft\TeamFoundation\Team Explorer\Microsoft.TeamFoundation.Common.dll</HintPath>
</Reference>
<Reference Include="Microsoft.TeamFoundation.Controls, Version=12.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\..\..\..\..\..\Program Files (x86)\Microsoft Visual Studio 14.0\Common7\IDE\CommonExtensions\Microsoft\TeamFoundation\Team Explorer\Microsoft.TeamFoundation.Controls.dll</HintPath>
</Reference>
<Reference Include="Microsoft.TeamFoundation.VersionControl.Client, Version=12.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\..\..\..\..\..\Program Files (x86)\Microsoft Visual Studio 14.0\Common7\IDE\CommonExtensions\Microsoft\TeamFoundation\Team Explorer\Microsoft.TeamFoundation.VersionControl.Client.dll</HintPath>
</Reference>
<Reference Include="Microsoft.TeamFoundation.VersionControl.Common, Version=12.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\..\..\..\..\..\Program Files (x86)\Microsoft Visual Studio 14.0\Common7\IDE\CommonExtensions\Microsoft\TeamFoundation\Team Explorer\Microsoft.TeamFoundation.VersionControl.Common.dll</HintPath>
</Reference>
<Reference Include="Microsoft.TeamFoundation.VersionControl.Controls, Version=12.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\..\..\..\..\..\Program Files (x86)\Microsoft Visual Studio 14.0\Common7\IDE\CommonExtensions\Microsoft\TeamFoundation\Team Explorer\Microsoft.TeamFoundation.VersionControl.Controls.dll</HintPath>
</Reference>
<Reference Include="Microsoft.VisualStudio.ComponentModelHost, Version=12.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL" />
<Reference Include="Microsoft.VisualStudio.CoreUtility, Version=12.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL" />
<Reference Include="Microsoft.VisualStudio.Language.Intellisense, Version=12.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL" />
@ -161,7 +186,10 @@
<Compile Include="..\..\script\SolutionInfo.cs">
<Link>Properties\SolutionInfo.cs</Link>
</Compile>
<Compile Include="Base\TeamExplorerBase.cs" />
<Compile Include="Helpers\ExportFactoryProvider.cs" />
<Compile Include="Guids.cs" />
<Compile Include="Base\PackageBase.cs" />
<Compile Include="Resources.Designer.cs">
<AutoGen>True</AutoGen>
<DesignTime>True</DesignTime>
@ -171,6 +199,7 @@
<Compile Include="GitHubPackage.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="PkgCmdID.cs" />
<Compile Include="Services\UIProvider.cs" />
<Compile Include="UI\DrawingExtensions.cs" />
<Compile Include="UI\Views\Controls\LoginControl.xaml.cs">
<DependentUpon>LoginControl.xaml</DependentUpon>
@ -245,6 +274,12 @@
<Project>{1a1da411-8d1f-4578-80a6-04576bea2dc5}</Project>
<Name>GitHub.App</Name>
</ProjectReference>
<ProjectReference Include="..\GitHub.Exports\GitHub.Exports.csproj">
<Project>{9aea02db-02b5-409c-b0ca-115d05331a6b}</Project>
<Name>GitHub.Exports</Name>
<IncludeOutputGroupsInVSIX>BuiltProjectOutputGroup%3bBuiltProjectOutputGroupDependencies%3bGetCopyToOutputDirectoryItems%3bSatelliteDllsProjectOutputGroup%3b</IncludeOutputGroupsInVSIX>
<IncludeOutputGroupsInVSIXLocalOnly>DebugSymbolsProjectOutputGroup%3b</IncludeOutputGroupsInVSIXLocalOnly>
</ProjectReference>
<ProjectReference Include="..\GitHub.Extensions.Reactive\GitHub.Extensions.Reactive.csproj">
<Project>{6559e128-8b40-49a5-85a8-05565ed0c7e3}</Project>
<Name>GitHub.Extensions.Reactive</Name>

Просмотреть файл

@ -20,6 +20,8 @@ using Microsoft.VisualStudio.ComponentModelHost;
using Microsoft.VisualStudio.Shell;
using ReactiveUI;
using Splat;
using GitHub.Exports;
using GitHub.VisualStudio.Services;
namespace GitHub.VisualStudio
{
@ -43,33 +45,15 @@ namespace GitHub.VisualStudio
[ProvideMenuResource("Menus.ctmenu", 1)]
[Guid(GuidList.guidGitHubPkgString)]
[ProvideBindingPath]
public class GitHubPackage : Package
public class GitHubPackage : PackageBase
{
readonly IServiceProvider serviceProvider;
// Set of assemblies we need to load early.
static readonly IEnumerable<string> earlyLoadAssemblies = new[] {
"Rothko.dll",
"GitHub.App.dll",
"GitHub.UI.Reactive.dll",
"GitHub.UI.dll"
};
/// <summary>
/// Default constructor of the package.
/// Inside this method you can place any initialization code that does not require
/// any Visual Studio service because at this point the package object is created but
/// not sited yet inside Visual Studio environment. The place to do all the other
/// initialization is the Initialize method.
/// </summary>
public GitHubPackage()
{
serviceProvider = this;
}
public GitHubPackage(IServiceProvider serviceProvider)
: base(serviceProvider)
{
this.serviceProvider = serviceProvider;
}
/// <summary>
@ -82,42 +66,16 @@ namespace GitHub.VisualStudio
Debug.WriteLine("Entering Initialize() of: {0}", ToString());
base.Initialize();
ModeDetector.OverrideModeDetector(new AppModeDetector());
RxApp.MainThreadScheduler = new DispatcherScheduler(Application.Current.Dispatcher);
var dir = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
Debug.Assert(dir != null, "The Assembly location can't be null");
foreach (var v in earlyLoadAssemblies)
{
Assembly.LoadFile(Path.Combine(dir, v));
}
// Set the Export Provider
var mefServiceProvider = GetExportedValue<IServiceProvider>() as MefServiceProvider;
Debug.Assert(mefServiceProvider != null, "Service Provider can't be imported");
var componentModel = (IComponentModel)(serviceProvider.GetService(typeof(SComponentModel)));
mefServiceProvider.ExportProvider = componentModel.DefaultExportProvider;
// Add our command handlers for menu (commands must exist in the .vsct file)
var mcs = serviceProvider.GetService(typeof(IMenuCommandService)) as IMenuCommandService;
if (mcs != null)
{
// Login Command Menu Item
AddTopLevelMenuItem(mcs, PkgCmdIDList.loginCommand, OnLoginCommand);
// Login Command Menu Item
// Create Issue Command Menu Item
AddTopLevelMenuItem(mcs, PkgCmdIDList.createIssueCommand, OnCreateIssueCommand);
}
}
AddTopLevelMenuItem(PkgCmdIDList.loginCommand, OnLoginCommand);
static void AddTopLevelMenuItem(
IMenuCommandService menuCommandService,
uint packageCommandId,
EventHandler eventHandler)
{
var menuCommandId = new CommandID(GuidList.guidGitHubCmdSet, (int)packageCommandId);
var menuItem = new MenuCommand(eventHandler, menuCommandId);
menuCommandService.AddCommand(menuItem);
// Create Issue Command Menu Item
AddTopLevelMenuItem(PkgCmdIDList.createIssueCommand, OnCreateIssueCommand);
}
/// <summary>
@ -125,7 +83,7 @@ namespace GitHub.VisualStudio
/// See the Initialize method to see how the menu item is associated to this function using
/// the OleMenuCommandService service and the MenuCommand class.
/// </summary>
static void OnCreateIssueCommand(object sender, EventArgs e)
void OnCreateIssueCommand(object sender, EventArgs e)
{
var createIssueDialog = new CreateIssueDialog();
createIssueDialog.ShowModal();
@ -133,41 +91,38 @@ namespace GitHub.VisualStudio
void OnLoginCommand(object sender, EventArgs e)
{
var loginControlViewModel = GetExportedValue<LoginControlViewModel>();
EnsureUIProvider();
/*
var mefServiceProvider = GetExportedValue<IServiceProvider>() as MefServiceProvider;
Debug.Assert(mefServiceProvider != null, "Service Provider can't be imported");
var componentModel = GetService<SComponentModel>() as IComponentModel;
if (componentModel != null)
mefServiceProvider.ExportProvider = componentModel.DefaultExportProvider;
*/
//var r = GetExportedValue<ILoginDialog>();
var factory = GetExportedValue<ExportFactoryProvider>().LoginViewModelFactory;
var disposable = factory.CreateExport();
var loginControlViewModel = disposable.Value;
var loginIssueDialog = new LoginCommandDialog(loginControlViewModel);
loginIssueDialog.Closed += (o,ev) => disposable.Dispose();
loginControlViewModel.CancelEvt.Subscribe(x => loginIssueDialog.Close());
loginIssueDialog.Show();
loginControlViewModel.AuthenticationResults.Subscribe(result =>
{
if (result == AuthenticationResult.Success)
loginIssueDialog.Hide();
{
loginIssueDialog.Close();
}
});
}
T GetExportedValue<T>()
{
var componentModel = (IComponentModel)(serviceProvider.GetService(typeof(SComponentModel)));
var exportProvider = componentModel.DefaultExportProvider;
return exportProvider.GetExportedValue<T>();
}
}
[Export(typeof(IServiceProvider))]
[PartCreationPolicy(CreationPolicy.Shared)]
public class MefServiceProvider : IServiceProvider
{
public ExportProvider ExportProvider { get; set; }
public object GetService(Type serviceType)
{
string contract = AttributedModelServices.GetContractName(serviceType);
var instance = ExportProvider.GetExportedValues<object>(contract).FirstOrDefault();
if (instance != null)
return instance;
throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture,
"Could not locate any instances of contract {0}.", contract));
}
}
}

Просмотреть файл

@ -0,0 +1,27 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace GitHub.Exports
{
[Export]
public class ExportFactoryProvider
{
[ImportingConstructor]
public ExportFactoryProvider(ICompositionService cc)
{
cc.SatisfyImportsOnce(this);
}
[Import(AllowRecomposition =true)]
public ExportFactory<ILoginDialog> LoginViewModelFactory { get; set; }
/*
[Import(AllowRecomposition = true)]
public ExportFactory<ITwoFactorDialog> TwoFactorViewModelFactory { get; set; }
*/
}
}

Просмотреть файл

@ -0,0 +1,55 @@
using GitHub.Infrastructure;
using ReactiveUI;
using Splat;
using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.ComponentModel.Composition.Hosting;
using System.Globalization;
using System.Linq;
using System.Reactive.Concurrency;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
namespace GitHub.VisualStudio.Services
{
[Export]
[PartCreationPolicy(CreationPolicy.Shared)]
public class UIProvider
{
[Import(typeof(IServiceProvider))]
public MefServiceProvider ServiceProvider { get; set; }
public UIProvider()
{
ModeDetector.OverrideModeDetector(new AppModeDetector());
RxApp.MainThreadScheduler = new DispatcherScheduler(Application.Current.Dispatcher);
}
public void EnsureProvider(ExportProvider provider)
{
if (ServiceProvider.ExportProvider == null)
ServiceProvider.ExportProvider = provider;
}
}
[Export(typeof(IServiceProvider))]
[PartCreationPolicy(CreationPolicy.Shared)]
public class MefServiceProvider : IServiceProvider
{
public ExportProvider ExportProvider { get; set; }
public object GetService(Type serviceType)
{
string contract = AttributedModelServices.GetContractName(serviceType);
var instance = ExportProvider.GetExportedValues<object>(contract).FirstOrDefault();
if (instance != null)
return instance;
throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture,
"Could not locate any instances of contract {0}.", contract));
}
}
}

Просмотреть файл

@ -2,45 +2,46 @@
using GitHub.ViewModels;
using NullGuard;
using ReactiveUI;
using GitHub.Exports;
namespace GitHub.VisualStudio.UI.Views.Controls
{
/// <summary>
/// Interaction logic for LoginControl.xaml
/// </summary>
public partial class LoginControl : IViewFor<LoginControlViewModel>
public partial class LoginControl : IViewFor<ILoginDialog>
{
public LoginControl()
{
InitializeComponent();
DataContextChanged += (s, e) => ViewModel = (LoginControlViewModel)e.NewValue;
DataContextChanged += (s, e) => ViewModel = (ILoginDialog)e.NewValue;
this.WhenActivated(d =>
{
d(this.Bind(ViewModel, vm => vm.UsernameOrEmail, v => v.usernameOrEmailTextBox.Text));
d(this.Bind(ViewModel, vm => vm.Password, v => v.passwordTextBox.Text));
d(this.BindCommand(ViewModel, vm => vm.LoginCommand, v => v.loginButton));
d(this.BindCommand(ViewModel, vm => vm.CancelCommand, v => v.cancelButton));
d(this.BindCommand(ViewModel, vm => vm.LoginCmd, v => v.loginButton));
d(this.BindCommand(ViewModel, vm => vm.CancelCmd, v => v.cancelButton));
});
VisualStateManager.GoToState(this, "DotCom", true);
}
public static readonly DependencyProperty ViewModelProperty = DependencyProperty.Register(
"ViewModel", typeof(LoginControlViewModel), typeof(LoginControl), new PropertyMetadata(null));
"ViewModel", typeof(ILoginDialog), typeof(LoginControl), new PropertyMetadata(null));
object IViewFor.ViewModel
{
get { return ViewModel; }
set { ViewModel = (LoginControlViewModel)value; }
set { ViewModel = (ILoginDialog)value; }
}
public LoginControlViewModel ViewModel
public ILoginDialog ViewModel
{
[return: AllowNull]
get { return (LoginControlViewModel)GetValue(ViewModelProperty); }
get { return (ILoginDialog)GetValue(ViewModelProperty); }
set { SetValue(ViewModelProperty, value); }
}
}

Просмотреть файл

@ -1,4 +1,4 @@
using GitHub.ViewModels;
using GitHub.Exports;
using Microsoft.VisualStudio.PlatformUI;
namespace GitHub.VisualStudio.UI.Views
@ -8,7 +8,7 @@ namespace GitHub.VisualStudio.UI.Views
/// </summary>
public partial class LoginCommandDialog : DialogWindow
{
public LoginCommandDialog(LoginControlViewModel loginControlViewModel)
public LoginCommandDialog(ILoginDialog loginControlViewModel)
{
InitializeComponent();

Просмотреть файл

@ -20,5 +20,6 @@
<Asset Type="Microsoft.VisualStudio.MefComponent" d:Source="Project" d:ProjectName="%CurrentProject%" Path="|%CurrentProject%|" />
<Asset Type="Microsoft.VisualStudio.MefComponent" d:Source="Project" d:ProjectName="GitHub.App" Path="|GitHub.App|" />
<Asset Type="Microsoft.VisualStudio.MefComponent" d:Source="Project" d:ProjectName="Rothko" Path="|Rothko|" />
<Asset Type="Microsoft.VisualStudio.MefComponent" d:Source="Project" d:ProjectName="GitHub.Exports" Path="|GitHub.Exports|" />
</Assets>
</PackageManifest>