다음을 통해 공유


연습: 프로젝트 템플릿을 사용하여 사이트 열 프로젝트 항목 만들기, 2부

Visual Studio에서 SharePoint 프로젝트 항목의 사용자 지정 형식을 정의하여 프로젝트 템플릿과 연결한 후에는 템플릿에 대한 마법사를 제공할 수 있습니다. 마법사를 사용하면 사용자가 템플릿을 사용하여 해당 프로젝트 항목이 포함된 새 프로젝트를 만들 때 사용자에게서 정보를 수집할 수 있습니다. 수집한 정보는 프로젝트 항목을 초기화하는 데 사용될 수 있습니다.

이 연습에서는 연습: 프로젝트 템플릿을 사용하여 사이트 열 프로젝트 항목 만들기, 1부에서 설명한 사이트 열 프로젝트 템플릿에 마법사를 추가합니다. 사용자가 사이트 열 프로젝트를 만들 때 마법사에서는 사이트 열에 대한 정보(예: 기본 형식 및 그룹)를 수집하고 이 정보를 새 프로젝트의 Elements.xml 파일에 추가합니다.

이 연습에서는 다음 작업을 수행합니다.

  • 프로젝트 템플릿과 연결된 사용자 지정 SharePoint 프로젝트 항목 형식을 위한 마법사 만들기

  • Visual Studio 2010에서 SharePoint 프로젝트를 위한 기본 제공 마법사와 유사한 사용자 지정 마법사 UI 정의

  • 마법사가 실행되는 동안 로컬 SharePoint 사이트를 호출하는 데 사용되는 두 가지 SharePoint 명령 만들기. SharePoint 명령은 Visual Studio 확장이 SharePoint 서버 개체 모델에서 API를 호출하기 위해 사용할 수 있는 메서드입니다. 자세한 내용은 SharePoint 개체 모델 호출을 참조하십시오.

  • 대체 가능한 매개 변수를 사용하여 마법사에서 수집한 데이터로 SharePoint 프로젝트 파일 초기화

  • 각각의 새 사이트 열 프로젝트 인스턴스에서 .snk 파일 새로 만들기 이 파일은 SharePoint 솔루션 어셈블리가 전역 어셈블리 캐시에 배포될 수 있도록 프로젝트 출력에 서명하는 데 사용됩니다.

  • 마법사 디버깅 및 테스트

참고

https://go.microsoft.com/fwlink/?LinkId=191369에서 이 연습에 대한 완료된 프로젝트, 코드 및 기타 파일이 포함된 샘플을 다운로드할 수 있습니다.

사전 요구 사항

이 연습을 수행하려면 먼저 연습: 프로젝트 템플릿을 사용하여 사이트 열 프로젝트 항목 만들기, 1부를 완료하여 SiteColumnProjectItem 솔루션을 만들어야 합니다.

또한 개발 컴퓨터에 다음 구성 요소가 있어야 이 연습을 완료할 수 있습니다.

다음 개념을 알고 있으면 연습을 완료하는 데 도움이 되지만 반드시 필요하지는 않습니다.

마법사 구성 요소 이해

이 연습에서 보여 주는 마법사에는 몇 가지 구성 요소가 포함되어 있습니다. 다음 표에서는 이러한 구성 요소에 대해 설명합니다.

구성 요소

설명

마법사 구현

IWizard 인터페이스를 구현하는 SiteColumnProjectWizard라는 클래스입니다. 이 인터페이스는 마법사가 시작되고 완료될 때와 마법사 실행 중의 특정한 때에 Visual Studio에서 호출하는 메서드를 정의합니다.

마법사 UI

WizardWindow라는 WPF 기반 창입니다. 이 창에는 Page1 및 Page2라는 두 가지 사용자 정의 컨트롤이 포함되어 있습니다. 이러한 사용자 정의 컨트롤은 마법사의 두 페이지를 나타냅니다.

이 연습에서 마법사 구현의 RunStarted 메서드는 마법사 UI를 표시합니다.

마법사 데이터 모델

마법사 UI와 마법사 구현 간의 계층을 제공하는 SiteColumnWizardModel이라는 중간 클래스입니다. 이 샘플에서는 이 클래스를 사용하여 마법사 구현과 마법사 UI를 서로 추상화합니다. 이 클래스는 모든 마법사의 필수 구성 요소가 아닙니다.

이 연습에서 마법사 구현은 마법사 UI를 표시할 때 SiteColumnWizardModel 개체를 마법사 창에 전달합니다. 마법사 UI는 이 개체의 메서드를 사용하여 UI의 컨트롤 값을 저장하고 입력 사이트 URL이 유효한지 확인하는 등의 작업을 수행합니다. 사용자가 마법사를 완료한 후 마법사 구현은 SiteColumnWizardModel 개체를 사용하여 UI의 최종 상태를 확인합니다.

프로젝트 서명 관리자

마법사 구현이 각각의 새 프로젝트 인스턴스에서 key.snk 파일을 새로 만드는 데 사용하는 ProjectSigningManager라는 도우미 클래스입니다.

SharePoint 명령

마법사가 실행되는 동안 마법사 데이터 모델에서 로컬 SharePoint 사이트를 호출하는 데 사용하는 메서드입니다. SharePoint 명령이 .NET Framework 3.5를 대상으로 해야 하기 때문에 이러한 명령은 마법사 코드의 나머지 부분과 다른 어셈블리에서 구현됩니다.

프로젝트 만들기

이 연습을 완료하려면 연습: 프로젝트 템플릿을 사용하여 사이트 열 프로젝트 항목 만들기, 1부에서 만든 SiteColumnProjectItem 솔루션에 몇 가지 프로젝트를 추가해야 합니다.

  • WPF 프로젝트. 이 프로젝트에서 IWizard 인터페이스를 구현하고 마법사 UI를 정의합니다.

  • SharePoint 명령을 정의하는 클래스 라이브러리 프로젝트. 이 프로젝트는 .NET Framework 3.5를 대상으로 해야 합니다.

먼저 프로젝트를 만들어 연습을 시작합니다.

WPF 프로젝트를 만들려면

  1. Visual Studio에서 SiteColumnProjectItem 솔루션을 엽니다.

  2. 솔루션 탐색기에서 솔루션 노드를 마우스 오른쪽 단추로 클릭하고 추가를 가리킨 다음 새 프로젝트를 클릭합니다.

    참고

    Visual Basic 프로젝트에서는 옵션 대화 상자, 프로젝트 및 솔루션, 일반에서 솔루션 항상 표시 확인란을 선택한 경우에만 솔루션 노드가 표시됩니다.

  3. 새 프로젝트 추가 대화 상자에서 Visual C# 또는 Visual Basic 노드를 확장하고 Windows를 클릭합니다.

  4. 새 프로젝트 추가 대화 상자 맨 위의 콤보 상자에서 .NET Framework 4가 선택되어 있는지 확인합니다.

  5. WPF 사용자 정의 컨트롤 라이브러리 프로젝트 템플릿을 선택합니다.

  6. 이름 상자에 ProjectTemplateWizard를 입력합니다.

  7. 확인을 클릭합니다.

    Visual Studio에서 ProjectTemplateWizard 프로젝트를 솔루션에 추가합니다.

  8. 프로젝트에서 UserControl1.xaml 파일을 삭제합니다.

SharePoint 명령 프로젝트를 만들려면

  1. 솔루션 탐색기에서 솔루션 노드를 마우스 오른쪽 단추로 클릭하고 추가를 가리킨 다음 새 프로젝트를 클릭합니다.

  2. 새 프로젝트 추가 대화 상자에서 Visual C# 또는 Visual Basic을 확장하고 Windows를 클릭합니다.

  3. 클래스 라이브러리 프로젝트 템플릿을 클릭합니다.

  4. 대화 상자 맨 위의 콤보 상자에서 .NET Framework 3.5를 선택합니다.

  5. 이름 상자에 SharePointCommands를 입력합니다.

  6. 확인을 클릭합니다.

    SharePointCommands 프로젝트가 솔루션에 추가되고 기본 Class1 코드 파일이 열립니다.

  7. 프로젝트에서 Class1 코드 파일을 삭제합니다.

프로젝트 구성

마법사를 만들기 전에 일부 코드 파일과 어셈블리 참조를 프로젝트에 추가해야 합니다.

마법사 프로젝트를 구성하려면

  1. 솔루션 탐색기에서 ProjectTemplateWizard 프로젝트 노드를 마우스 오른쪽 단추로 클릭하고 속성을 클릭합니다.

  2. 프로젝트 디자이너에서 대상 프레임워크를 .NET Framework 4 Client Profile에서 .NET Framework 4로 변경합니다. Visual C# 프로젝트에서는 응용 프로그램 탭에서 이 작업을 수행할 수 있으며, Visual Basic 프로젝트의 경우에는 컴파일 탭에서 이 작업을 수행할 수 있습니다. 자세한 내용은 방법: 특정 .NET Framework 버전 또는 프로필을 대상으로 지정을 참조하십시오.

    참고

    기본적으로 .NET Framework 4를 대상으로 하는 새 프로젝트를 만들면 프로젝트가 클라이언트 프로필을 대상으로 합니다. 이 연습에서는 완전한 .NET Framework 4가 필요합니다.

  3. ProjectTemplateWizard 프로젝트에서 새 창(WPF) 항목을 프로젝트에 추가합니다. 항목의 이름을 WizardWindow로 지정합니다.

  4. 두 가지 새로운 사용자 정의 컨트롤(WPF) 항목을 프로젝트에 추가합니다. 이러한 항목의 이름을 Page1 및 Page2로 지정합니다.

  5. 다음과 같은 이름의 코드 파일 네 개를 추가합니다.

    • SiteColumnProjectWizard

    • SiteColumnWizardModel

    • ProjectSigningManager

    • CommandIds

  6. 프로젝트 메뉴에서 참조 추가를 클릭합니다.

  7. .NET 탭에서 Ctrl 키를 누르고 다음 어셈블리를 클릭한 다음 확인을 클릭합니다.

    • EnvDTE

    • Microsoft.VisualStudio.OLE.Interop

    • Microsoft.VisualStudio.SharePoint

    • Microsoft.VisualStudio.Shell.10.0

    • Microsoft.VisualStudio.Shell.Interop.10.0

    • Microsoft.VisualStudio.TemplateWizardInterface

  8. 솔루션 탐색기에서 ProjectTemplateWizard 프로젝트의 참조 폴더 아래에서 EnvDTE를 클릭합니다.

    참고

    Visual Basic 프로젝트에서 참조 폴더를 보려면 솔루션 탐색기에서 모든 파일 표시 단추를 클릭해야 합니다.

  9. 속성 창에서 Interop 형식 포함 속성을 False로 변경합니다.

  10. Visual Basic 프로젝트를 개발하는 중이면 프로젝트 디자이너를 사용하여 ProjectTemplateWizard 네임스페이스를 프로젝트로 가져옵니다. 자세한 내용은 방법: 가져온 네임스페이스 추가 또는 제거(Visual Basic)를 참조하십시오.

SharePointCommands 프로젝트를 구성하려면

  1. SharePointCommands 프로젝트에 Commands라는 코드 파일을 추가합니다.

  2. 솔루션 탐색기에서 SharePointCommands 프로젝트 노드를 클릭합니다.

  3. 프로젝트 메뉴에서 기존 항목 추가를 선택합니다.

  4. 기존 항목 추가 대화 상자에서 ProjectTemplateWizard 프로젝트의 코드 파일이 포함된 폴더로 이동합니다.

  5. CommandIds 코드 파일을 선택합니다.

  6. 추가 단추의 드롭다운 메뉴를 클릭하고 링크로 추가를 선택합니다.

    Visual Studio에서 코드 파일을 SharePointCommands 프로젝트에 링크로 추가합니다. 즉, 코드 파일은 ProjectTemplateWizard 프로젝트에 있지만 파일의 코드는 SharePointCommands 프로젝트에도 컴파일됩니다.

  7. 프로젝트 메뉴에서 참조 추가를 클릭합니다.

  8. .NET 탭에서 Ctrl 키를 누르고 다음 어셈블리를 선택한 다음 확인을 클릭합니다.

    • Microsoft.SharePoint

    • Microsoft.VisualStudio.SharePoint.Commands

마법사 모델, 서명 관리자 및 SharePoint 명령 ID 만들기

샘플에서 다음 구성 요소를 구현하는 코드를 ProjectTemplateWizard 프로젝트에 추가합니다.

  • SharePoint 명령 ID. 마법사에서 사용하는 SharePoint 명령을 식별하는 문자열입니다. 이 연습의 뒷부분에서 이러한 명령을 구현하는 코드를 SharePointCommands 프로젝트에 추가합니다.

  • 마법사 데이터 모델

  • 프로젝트 서명 관리자

이러한 구성 요소에 대한 자세한 내용은 마법사 구성 요소 이해를 참조하십시오.

SharePoint 명령 ID를 정의하려면

  1. ProjectTemplateWizard 프로젝트에서 CommandIds 코드 파일을 엽니다.

  2. 이 파일의 전체 내용을 다음 코드로 바꿉니다.

    Namespace Contoso.SharePoint.Commands
        Public Class CommandIds
            Public Const GetFieldTypes As String = "Contoso.Commands.GetFieldTypes"
            Public Const ValidateSite As String = "Contoso.Commands.ValidateSite"
        End Class
    End Namespace
    
    namespace Contoso.SharePoint.Commands
    {
        public static class CommandIds
        {
            public const string GetFieldTypes = "Contoso.Commands.GetFieldTypes";
            public const string ValidateSite = "Contoso.Commands.ValidateSite";
        }
    }
    

마법사 모델을 만들려면

  1. SiteColumnWizardModel 코드 파일을 엽니다.

  2. 이 파일의 전체 내용을 다음 코드로 바꿉니다.

    Imports EnvDTE
    Imports Microsoft.VisualStudio.SharePoint
    Imports Microsoft.VisualStudio
    Imports Microsoft.VisualStudio.Shell
    Imports Microsoft.VisualStudio.Shell.Interop
    Imports IOleServiceProvider = Microsoft.VisualStudio.OLE.Interop.IServiceProvider
    
    Public Class SiteColumnWizardModel
        Private dteObject As DTE
        Private projectServiceValue As ISharePointProjectService
        Private validatedUrls As New List(Of String)
    
        Friend Sub New(ByVal dteObject As DTE, ByVal requiresFarmPriveleges As Boolean)
            Me.dteObject = dteObject
    
            ' Initialize default values for wizard choices.
            IsSandboxed = Not requiresFarmPriveleges
            IsSecondPagePopulated = False
            FieldType = "Text"
            FieldGroup = "Custom Columns"
            FieldName = "My Custom Column"
            CurrentSiteUrl = GetLocalHostUrl()
        End Sub
    
    #Region "Helper methods used by the wizard UI"
    
        ' Specifies whether the current site URL is valid. Uses the ValidateSite SharePoint command to do this.
        Friend Function ValidateCurrentUrl(ByVal errorMessage As String) As Boolean
            Dim isValid As Boolean = False
            errorMessage = String.Empty
    
            If validatedUrls.Contains(CurrentSiteUrl) Then
                isValid = True
            Else
                Dim uriToValidate As Uri = New Uri(CurrentSiteUrl, UriKind.Absolute)
                Dim vsThreadedWaitDialog As IVsThreadedWaitDialog2 = Nothing
    
                Try
                    vsThreadedWaitDialog = ShowProgressDialog("Connect to SharePoint",
                        "Connecting to SharePoint site " + CurrentSiteUrl)
                    isValid = Me.ProjectService.SharePointConnection.ExecuteCommand(Of Uri, Boolean)(
                        Contoso.SharePoint.Commands.CommandIds.ValidateSite, uriToValidate)
                Catch ex As Exception
                    errorMessage = "An error occurred while validating the site. " + ex.Message
                Finally
                    If isValid Then
                        validatedUrls.Add(CurrentSiteUrl)
                    End If
                    If vsThreadedWaitDialog IsNot Nothing Then
                        CloseProgressDialog(vsThreadedWaitDialog)
                    End If
                End Try
            End If
            Return isValid
        End Function
    
        ' Gets the available field types from the SharePoint site. Uses the GetFieldTypes SharePoint command to do this.
        Friend Function GetFieldTypes() As ArrayList
            ' If we have not yet validated this site, do it now.
            Dim errorMessage As String = String.Empty
            If Not ValidateCurrentUrl(errorMessage) Then
                MessageBox.Show(String.Format("Cannot connect to the SharePoint site: {0}. {1}",
                    CurrentSiteUrl, errorMessage), "SharePoint Connection Error")
                Return Nothing
            End If
    
            ' Site is valid, so go ahead and get the available field types.
            Dim siteUri As Uri = New Uri(CurrentSiteUrl, UriKind.Absolute)
            Dim vsThreadedWaitDialog As IVsThreadedWaitDialog2 = ShowProgressDialog(
                "Connect to SharePoint", "Connecting to SharePoint site " + CurrentSiteUrl)
            Dim fieldTypesArray As String() = Me.ProjectService.SharePointConnection.ExecuteCommand(Of Uri, String())(
                Contoso.SharePoint.Commands.CommandIds.GetFieldTypes, siteUri)
    
            If vsThreadedWaitDialog IsNot Nothing Then
                CloseProgressDialog(vsThreadedWaitDialog)
            End If
    
            Return New ArrayList(fieldTypesArray)
        End Function
    
        ' Returns the default column group names in SharePoint.
        Friend Function GetFieldGroups() As List(Of String)
            Dim groups As List(Of String) = New List(Of String)()
            groups.Add("Base Columns")
            groups.Add("Core Contact and Calendar Columns")
            groups.Add("Core Document Columns")
            groups.Add("Core Task and Issue Columns")
            groups.Add("Extended Columns")
            Return groups
        End Function
    #End Region
    
    #Region "Properties shared by the wizard implementation and the wizard UI"
    
        Friend ReadOnly Property ProjectService As ISharePointProjectService
            Get
                If projectServiceValue Is Nothing Then
                    projectServiceValue = GetProjectService()
                End If
                Return projectServiceValue
            End Get
        End Property
    
        Friend Property IsSecondPagePopulated As Boolean
        Friend Property IsSandboxed As Boolean
        Friend Property FieldType As String
        Friend Property FieldGroup As String
        Friend Property FieldName As String
        Friend Property CurrentSiteUrl As String
    #End Region
    
    #Region "Private methods"
    
        Private Function GetLocalHostUrl() As String
            Const HttpScheme As String = "http"
            Dim builder As UriBuilder = New UriBuilder(HttpScheme, Environment.MachineName.ToLowerInvariant())
            Return builder.ToString()
        End Function
    
        Private Function GetProjectService() As ISharePointProjectService
            Dim serviceProvider As ServiceProvider = New ServiceProvider(CType(dteObject, IOleServiceProvider))
            Return CType(serviceProvider.GetService(GetType(ISharePointProjectService)), ISharePointProjectService)
        End Function
    
        Private Function ShowProgressDialog(ByVal caption As String, ByVal message As String) As IVsThreadedWaitDialog2
            Dim oleServiceProvider As IOleServiceProvider = CType(dteObject, IOleServiceProvider)
            Dim dialogFactory As IVsThreadedWaitDialogFactory = CType(New ServiceProvider(oleServiceProvider).GetService(
                GetType(SVsThreadedWaitDialogFactory)), IVsThreadedWaitDialogFactory)
    
            If dialogFactory Is Nothing Then
                Throw New InvalidOperationException("The IVsThreadedWaitDialogFactory object could not be retrieved.")
            End If
    
            Dim vsThreadedWaitDialog As IVsThreadedWaitDialog2 = Nothing
            ErrorHandler.ThrowOnFailure(dialogFactory.CreateInstance(vsThreadedWaitDialog))
            ErrorHandler.ThrowOnFailure(vsThreadedWaitDialog.StartWaitDialog(caption, message,
                Nothing, Nothing, String.Empty, 0, False, True))
            Return vsThreadedWaitDialog
        End Function
    
        Private Sub CloseProgressDialog(ByVal vsThreadedWaitDialog As IVsThreadedWaitDialog2)
            If vsThreadedWaitDialog Is Nothing Then
                Throw New ArgumentNullException("vsThreadedWaitDialog")
            End If
            Dim canceled As Integer
            ErrorHandler.ThrowOnFailure(vsThreadedWaitDialog.EndWaitDialog(canceled))
        End Sub
    #End Region
    End Class
    
    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.Windows;
    using EnvDTE;
    using Microsoft.VisualStudio.SharePoint;
    using Microsoft.VisualStudio;
    using Microsoft.VisualStudio.Shell;
    using Microsoft.VisualStudio.Shell.Interop;
    using IOleServiceProvider = Microsoft.VisualStudio.OLE.Interop.IServiceProvider;
    
    namespace ProjectTemplateWizard
    {
        internal class SiteColumnWizardModel
        {
            private DTE dteObject;
            private ISharePointProjectService projectServiceValue;
            private List<string> validatedUrls = new List<string>();
    
            internal SiteColumnWizardModel(DTE dteObject, bool requiresFarmPriveleges)
            {
                this.dteObject = dteObject;
    
                // Initialize default values for wizard choices.
                IsSandboxed = !requiresFarmPriveleges;
                IsSecondPagePopulated = false;
                FieldType = "Text";
                FieldGroup = "Custom Columns";
                FieldName = "My Custom Column";
                CurrentSiteUrl = GetLocalHostUrl();
            }
    
            #region Helper methods used by the wizard UI
    
            // Specifies whether the current site URL is valid. Uses the ValidateSite SharePoint command to do this.
            internal bool ValidateCurrentUrl(out string errorMessage)
            {
                bool isValid = false;
                errorMessage = String.Empty;
    
                if (validatedUrls.Contains(CurrentSiteUrl))
                {
                    isValid = true;
                }
                else
                {
                    Uri uriToValidate = new Uri(CurrentSiteUrl, UriKind.Absolute);
                    IVsThreadedWaitDialog2 vsThreadedWaitDialog = null;
    
                    try
                    {
                        vsThreadedWaitDialog = ShowProgressDialog("Connect to SharePoint",
                            "Connecting to SharePoint site " + CurrentSiteUrl);
                        isValid = this.ProjectService.SharePointConnection.ExecuteCommand<Uri, bool>(
                            Contoso.SharePoint.Commands.CommandIds.ValidateSite, uriToValidate);
                    }
                    catch (Exception ex)
                    {
                        errorMessage = "An error occurred while validating the site. " + ex.Message;
                    }
                    finally
                    {
                        if (isValid)
                        {
                            validatedUrls.Add(CurrentSiteUrl);
                        }
    
                        if (vsThreadedWaitDialog != null)
                        {
                            CloseProgressDialog(vsThreadedWaitDialog);
                        }
                    }
                }
    
                return isValid;
            }
    
            // Gets the available field types from the SharePoint site. Uses the GetFieldTypes SharePoint command to do this.
            internal ArrayList GetFieldTypes()
            {
                // If we have not yet validated this site, do it now.
                string errorMessage;
                if (!ValidateCurrentUrl(out errorMessage))
                {
                    MessageBox.Show(String.Format("Cannot connect to the SharePoint site: {0}. {1}",
                        CurrentSiteUrl, errorMessage), "SharePoint Connection Error");
                    return null;
                }
    
                // Site is valid, so go ahead and get the available field types.
                Uri siteUri = new Uri(CurrentSiteUrl, UriKind.Absolute);
                IVsThreadedWaitDialog2 vsThreadedWaitDialog = ShowProgressDialog(
                    "Connect to SharePoint", "Connecting to SharePoint site " + CurrentSiteUrl);
                string[] fieldTypesArray = this.ProjectService.SharePointConnection.ExecuteCommand<Uri, string[]>(
                        Contoso.SharePoint.Commands.CommandIds.GetFieldTypes, siteUri);
    
                if (vsThreadedWaitDialog != null)
                {
                    CloseProgressDialog(vsThreadedWaitDialog);
                }
    
                return new ArrayList(fieldTypesArray);
            }
    
            // Returns the default column group names in SharePoint.
            internal List<string> GetFieldGroups()
            {
                List<string> groups = new List<string>();
                groups.Add("Base Columns");
                groups.Add("Core Contact and Calendar Columns");
                groups.Add("Core Document Columns");
                groups.Add("Core Task and Issue Columns");
                groups.Add("Extended Columns");
                return groups;
            }
    
            #endregion
    
            #region Properties shared by the wizard implementation and the wizard UI
    
            internal ISharePointProjectService ProjectService
            {
                get
                {
                    if (projectServiceValue == null)
                    {
                        projectServiceValue = GetProjectService();
                    }
                    return projectServiceValue;
                }
            }
    
            internal bool IsSecondPagePopulated { get; set; }
            internal bool IsSandboxed { get; set; }
            internal string FieldType { get; set; }
            internal string FieldGroup { get; set; }
            internal string FieldName { get; set; }
            internal string CurrentSiteUrl { get; set; }
    
            #endregion
    
            #region Private methods
    
            private string GetLocalHostUrl()
            {
                const string HttpScheme = "http";
                UriBuilder builder = new UriBuilder(HttpScheme, Environment.MachineName.ToLowerInvariant());
                return builder.ToString();
            }
    
            private ISharePointProjectService GetProjectService()
            {
                ServiceProvider serviceProvider = new ServiceProvider(dteObject as IOleServiceProvider);
                return serviceProvider.GetService(typeof(ISharePointProjectService)) as ISharePointProjectService;
            }
    
            private IVsThreadedWaitDialog2 ShowProgressDialog(string caption, string message)
            {
                IOleServiceProvider oleServiceProvider = dteObject as IOleServiceProvider;
                IVsThreadedWaitDialogFactory dialogFactory = new ServiceProvider(oleServiceProvider).GetService(
                    typeof(SVsThreadedWaitDialogFactory)) as IVsThreadedWaitDialogFactory;
    
                if (dialogFactory == null)
                {
                    throw new InvalidOperationException("The IVsThreadedWaitDialogFactory object could not be retrieved.");
                }
    
                IVsThreadedWaitDialog2 vsThreadedWaitDialog = null;
                ErrorHandler.ThrowOnFailure(dialogFactory.CreateInstance(out vsThreadedWaitDialog));
                ErrorHandler.ThrowOnFailure(vsThreadedWaitDialog.StartWaitDialog(caption, message,
                     null, null, String.Empty, 0, false, true));
                return vsThreadedWaitDialog;
            }
    
            private void CloseProgressDialog(IVsThreadedWaitDialog2 vsThreadedWaitDialog)
            {
                if (vsThreadedWaitDialog == null)
                {
                    throw new ArgumentNullException("vsThreadedWaitDialog");
                }
    
                int canceled;
                ErrorHandler.ThrowOnFailure(vsThreadedWaitDialog.EndWaitDialog(out canceled));
            }
    
            #endregion
        }
    }
    

프로젝트 서명 관리자를 만들려면

  1. ProjectSigningManager 코드 파일을 엽니다.

  2. 이 파일의 전체 내용을 다음 코드로 바꿉니다.

    Imports EnvDTE
    Imports System
    Imports System.IO
    Imports System.Runtime.InteropServices
    
    Friend Class ProjectSigningManager
        Private Const KEY_FILENAME As String = "key.snk"
        Private keyBuffer As Byte()
        Private strongNameGenerated As Boolean = False
    
    #Region "Methods used by the project wizard"
    
        Friend Sub GenerateKeyFile()
            If Not strongNameGenerated Then
                keyBuffer = CreateNewKeyPair()
                strongNameGenerated = True
            End If
        End Sub
    
        Friend Sub AddKeyFile(ByVal project As Project)
            If strongNameGenerated Then
                AddKeyFileToProject(project)
            End If
        End Sub
    #End Region
    
    #Region "Private members"
    
        Private Function CreateNewKeyPair() As Byte()
            Dim buffer As IntPtr = IntPtr.Zero
            Dim bufferSize As UInteger
            Dim keyBuffer As Byte()
    
            Try
                If 0 = NativeMethods.StrongNameKeyGen(IntPtr.Zero, 0, buffer, bufferSize) Then
                    Marshal.ThrowExceptionForHR(NativeMethods.StrongNameErrorInfo())
                End If
                If buffer = IntPtr.Zero Then
                    Throw New InvalidOperationException("Cannot generate the strong name key.")
                End If
    
                ' Copy generated key to managed memory.
                keyBuffer = New Byte(bufferSize) {}
                Marshal.Copy(buffer, keyBuffer, 0, CInt(bufferSize))
            Finally
                ' Free native resources.
                NativeMethods.StrongNameFreeBuffer(buffer)
            End Try
            Return keyBuffer
        End Function
    
        Private Sub AddKeyFileToProject(ByVal project As Project)
    
            ' Save the key to a file.
            If keyBuffer IsNot Nothing Then
                Try
                    Dim destinationDirectory As String = Path.GetDirectoryName(project.FullName)
                    Dim keySavePath As String = Path.Combine(destinationDirectory, KEY_FILENAME)
    
                    File.WriteAllBytes(keySavePath, keyBuffer)
                    project.ProjectItems.AddFromFile(keySavePath)
    
                    ' Add properties in the project to use the key for signing.
                    Dim projProps As EnvDTE.Properties = project.Properties
                    projProps.Item("SignAssembly").Value = True
                    projProps.Item("AssemblyOriginatorKeyFile").Value = KEY_FILENAME
                Catch e As Exception
                    Throw New Exception("Cannot add the strong name key to the project. " & e.Message, e)
                End Try
            End If
        End Sub
    
        Private Class NativeMethods
            <DllImport("mscoree.dll")>
            Friend Shared Function StrongNameFreeBuffer(ByVal pbMemory As IntPtr) As Integer
            End Function
    
            <DllImport("mscoree.dll", CharSet:=CharSet.Unicode, ExactSpelling:=True)>
            Friend Shared Function StrongNameKeyGen(ByVal wszKeyContainer As IntPtr, ByVal dwFlags As UInteger, _
                ByRef KeyBlob As IntPtr, ByRef KeyBlobSize As UInteger) As Integer
            End Function
    
            <DllImport("mscoree.dll", CharSet:=CharSet.Unicode)>
            Friend Shared Function StrongNameErrorInfo() As Integer
            End Function
        End Class
    #End Region
    End Class
    
    using EnvDTE;
    using System;
    using System.IO;
    using System.Runtime.InteropServices;
    
    namespace ProjectTemplateWizard
    {
        internal class ProjectSigningManager
        {
            private const string KEY_FILENAME = "key.snk";
            private byte[] keyBuffer;
            private bool strongNameGenerated = false;
    
            #region Methods used by the project wizard
    
            internal void GenerateKeyFile()
            {
                if (!strongNameGenerated)
                {
                    keyBuffer = CreateNewKeyPair();
                    strongNameGenerated = true;
                }
            }
    
            internal void AddKeyFile(Project project)
            {
                if (strongNameGenerated)
                {
                    AddKeyFileToProject(project);
                }
            }
    
            #endregion
    
            #region Private members
    
            private byte[] CreateNewKeyPair()
            {
                IntPtr buffer = IntPtr.Zero;
                uint bufferSize;
                byte[] keyBuffer;
    
                try
                {
                    if (0 == NativeMethods.StrongNameKeyGen(IntPtr.Zero, 0, out buffer, out bufferSize))
                    {
                        Marshal.ThrowExceptionForHR(NativeMethods.StrongNameErrorInfo());
                    }
    
                    if (buffer == IntPtr.Zero)
                    {
                        throw new InvalidOperationException("Cannot generate the strong name key.");
                    }
    
                    // Copy generated key to managed memory.
                    keyBuffer = new byte[bufferSize];
                    Marshal.Copy(buffer, keyBuffer, 0, (int)bufferSize);
                }
                finally
                {
                    // Free native resources.
                    NativeMethods.StrongNameFreeBuffer(buffer);
                }
    
                return keyBuffer;
            }
    
            private void AddKeyFileToProject(Project project)
            {
                // Save the key to a file.
                if (keyBuffer != null)
                {
                    try
                    {
                        string destinationDirectory = Path.GetDirectoryName(project.FullName);
                        string keySavePath = Path.Combine(destinationDirectory, KEY_FILENAME);
    
                        File.WriteAllBytes(keySavePath, keyBuffer);
                        project.ProjectItems.AddFromFile(keySavePath);
    
                        // Add properties in the project to use the key for signing.
                        EnvDTE.Properties projProps = project.Properties;
                        projProps.Item("SignAssembly").Value = true;
                        projProps.Item("AssemblyOriginatorKeyFile").Value = KEY_FILENAME;
                    }
                    catch (Exception e)
                    {
                        throw new Exception("Cannot add the strong name key to the project. " + e.Message, e);
                    }
                }
            }
    
            private static class NativeMethods
            {
                [DllImport("mscoree.dll")]
                internal extern static int StrongNameFreeBuffer(IntPtr pbMemory);
    
                [DllImport("mscoree.dll", CharSet = CharSet.Unicode, ExactSpelling = true)]
                internal static extern int StrongNameKeyGen(IntPtr wszKeyContainer, uint dwFlags, out IntPtr KeyBlob, 
                    out uint KeyBlobSize);
    
                [DllImport("mscoree.dll", CharSet = CharSet.Unicode)]
                internal static extern int StrongNameErrorInfo();
            }
    
            #endregion
        }
    }
    

마법사 UI 만들기

마법사 창의 UI 및 마법사 페이지에 UI를 제공하는 두 가지 사용자 정의 컨트롤을 정의하는 XAML을 추가하고 창과 사용자 정의 컨트롤의 동작을 정의하는 코드를 추가합니다. 만드는 마법사는 Visual Studio 2010의 SharePoint 프로젝트에 대한 기본 제공 마법사와 유사합니다.

참고

다음 단계에서는 XAML 또는 코드를 프로젝트에 추가한 후 프로젝트에서 컴파일 오류가 발생합니다. 이러한 오류는 이후 단계에서 코드를 추가하면 사라집니다.

마법사 창 UI를 만들려면

  1. ProjectTemplateWizard 프로젝트에서 WizardWindow.xaml 파일을 두 번 클릭하여 디자이너에서 창을 엽니다.

  2. 디자이너의 XAML 뷰에서 현재 XAML을 다음 XAML로 바꿉니다. 이 XAML에서는 제목, 마법사 페이지가 포함된 Grid 및 창 아래쪽의 탐색 단추가 포함된 UI를 정의합니다.

    <ui:DialogWindow x:Class="ProjectTemplateWizard.WizardWindow"
                     xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
                     xmlns:ui="clr-namespace:Microsoft.VisualStudio.PlatformUI;assembly=Microsoft.VisualStudio.Shell.10.0"        
                     xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"                                
                     Title="SharePoint Customization Wizard" Height="500" Width="700" ResizeMode="NoResize" 
                     Loaded="Window_Loaded" TextOptions.TextFormattingMode="Display">
        <Grid>
            <Grid.RowDefinitions>
                <RowDefinition Height="75*" />
                <RowDefinition Height="364*" />
                <RowDefinition Height="1*" />
                <RowDefinition Height="60*" />
            </Grid.RowDefinitions>
            <Grid Grid.Row="0" Name="headingGrid" Background="White">
                <Label Height="28" HorizontalAlignment="Left" VerticalAlignment="Center" Margin="18,0,0,0" 
                       Name="headingLabel" FontWeight="ExtraBold" />
            </Grid>
            <Grid Grid.Row="1" Name="pageGrid" />
            <Rectangle Grid.Row="2" Name="separatorRectangle" Fill="White"  />
            <StackPanel Grid.Row="3" Name="navigationPanel" Orientation="Horizontal">
                <Button Content="&lt; _Previous" Margin="300,0,0,0"  Height="25" Name="previousButton" Width="85" 
                        IsEnabled="False" Click="previousButton_Click" />
                <Button Content="_Next >" Margin="10,0,0,0" Height="25" Name="nextButton" Width="85" Click="nextButton_Click" 
                        IsDefault="True" />
                <Button Content="_Finish" Margin="10,0,0,0" Height="25" Name="finishButton" Width="85" 
                        Click="finishButton_Click" />
                <Button Content="Cancel" Margin="10,0,0,0" Height="25" Name="cancelButton" Width="85" 
                        IsCancel="True" />
            </StackPanel>
        </Grid>
    </ui:DialogWindow>
    

    참고

    이 XAML에서 만들어지는 창은 DialogWindow 기본 클래스에서 파생됩니다. 사용자 지정 WPF 대화 상자를 Visual Studio에 추가하는 경우 다른 Visual Studio 대화 상자와 일관성 있는 스타일을 사용하고 발생할 수 있는 모달 대화 상자 문제를 방지하기 위해 이 클래스에서 대화 상자를 파생시키는 것이 좋습니다. 자세한 내용은 How to: Create and Manage Dialog Boxes를 참조하십시오.

  3. Visual Basic 프로젝트를 개발하는 중이면 Window 요소의 x:Class 특성에 있는 WizardWindow 클래스 이름에서 ProjectTemplateWizard 네임스페이스를 제거합니다. 이 네임스페이스는 XAML의 첫 번째 줄에 있습니다. 지금까지 작업을 마친 후의 첫 번째 줄은 다음과 같습니다.

    <Window x:Class="WizardWindow"
    
  4. WizardWindow.xaml 파일의 코드 숨김 파일을 엽니다.

  5. 이 파일의 전체 내용을 다음 코드로 바꿉니다.

    Public Class WizardWindow
        Private firstPage As Page1
        Private secondPage As Page2
        Private Const firstPageLabel As String = "Specify the site and security level for debugging"
        Private Const secondPageLabel As String = "Configure the site column"
    
        Friend Sub New(ByVal presentationModel As SiteColumnWizardModel)
            InitializeComponent()
            Me.PresentationModel = presentationModel
            firstPage = New Page1(Me)
            secondPage = New Page2(Me)
            secondPage.Visibility = Visibility.Hidden
        End Sub
    
        Friend Property PresentationModel As SiteColumnWizardModel
    
        Private Sub Window_Loaded(ByVal sender As Object, ByVal e As RoutedEventArgs)
            headingLabel.Content = firstPageLabel
            pageGrid.Children.Add(firstPage)
            pageGrid.Children.Add(secondPage)
        End Sub
    
        Private Sub nextButton_Click(ByVal sender As Object, ByVal e As RoutedEventArgs)
    
            ' Initialize the second wizard page if this is the first time 
            ' it has been shown with the current site URL.
            If Not PresentationModel.IsSecondPagePopulated Then
    
                If Not ValidateUrl() Then
                    Return
                End If
    
                ' Refresh the UI in the second page.
                secondPage.ClearControls()
                secondPage.PopulateSiteColumnOptions()
    
                ' Do not do this work again until the user changes the site URL.
                PresentationModel.IsSecondPagePopulated = True
            End If
    
            ' Display the second wizard page and update related controls.
            firstPage.Visibility = Visibility.Hidden
            secondPage.Visibility = Visibility.Visible
            previousButton.IsEnabled = True
            nextButton.IsEnabled = False
            nextButton.IsDefault = False
            finishButton.IsDefault = True
            headingLabel.Content = secondPageLabel
        End Sub
    
        ' Display the first wizard page again and update related controls.
        Private Sub previousButton_Click(ByVal sender As Object, ByVal e As RoutedEventArgs)
            secondPage.Visibility = Visibility.Hidden
            firstPage.Visibility = Visibility.Visible
            previousButton.IsEnabled = False
            finishButton.IsDefault = False
            nextButton.IsEnabled = True
            nextButton.IsDefault = True
            headingLabel.Content = firstPageLabel
        End Sub
    
        Private Sub finishButton_Click(ByVal sender As Object, ByVal e As RoutedEventArgs)
            If ValidateUrl() Then
                DialogResult = True
                Close()
            End If
        End Sub
    
        Private Function ValidateUrl() As Boolean
            Dim errorMessage As String = String.Empty
            If Not PresentationModel.ValidateCurrentUrl(errorMessage) Then
                MessageBox.Show(String.Format("Cannot connect to the SharePoint site: {0}. {1}",
                    PresentationModel.CurrentSiteUrl, errorMessage),
                    "SharePoint Connection Error")
                Return False
            End If
            Return True
        End Function
    End Class
    
    using System;
    using System.Windows;
    using Microsoft.VisualStudio.PlatformUI;
    
    namespace ProjectTemplateWizard
    {
        public partial class WizardWindow : DialogWindow
        {
            private Page1 firstPage;
            private Page2 secondPage;
            private const string firstPageLabel = "Specify the site and security level for debugging";
            private const string secondPageLabel = "Configure the site column";
    
            internal WizardWindow(SiteColumnWizardModel presentationModel)
            {
                InitializeComponent();
                this.PresentationModel = presentationModel;
                firstPage = new Page1(this);
                secondPage = new Page2(this);
                secondPage.Visibility = Visibility.Hidden;
            }
    
            internal SiteColumnWizardModel PresentationModel { get; set; }
    
            private void Window_Loaded(object sender, RoutedEventArgs e)
            {
                headingLabel.Content = firstPageLabel;
                pageGrid.Children.Add(firstPage);
                pageGrid.Children.Add(secondPage);
            }
    
            private void nextButton_Click(object sender, RoutedEventArgs e)
            {
                // Initialize the second wizard page if this is the first time 
                // it has been shown with the current site URL.
                if (!PresentationModel.IsSecondPagePopulated)
                {
                    if (!ValidateUrl())
                    {
                        return;
                    }
    
                    // Refresh the UI in the second page.
                    secondPage.ClearControls();
                    secondPage.PopulateSiteColumnOptions();
    
                    // Do not do this work again until the user changes the site URL.
                    PresentationModel.IsSecondPagePopulated = true;
                }
    
                // Display the second wizard page and update related controls.
                firstPage.Visibility = Visibility.Hidden;
                secondPage.Visibility = Visibility.Visible;
                previousButton.IsEnabled = true;
                nextButton.IsEnabled = false;
                finishButton.IsDefault = true;
                headingLabel.Content = secondPageLabel;
            }
    
            // Display the first wizard page again and update related controls.
            private void previousButton_Click(object sender, RoutedEventArgs e)
            {
                secondPage.Visibility = Visibility.Hidden;
                firstPage.Visibility = Visibility.Visible;
                previousButton.IsEnabled = false;
                finishButton.IsDefault = false;
                nextButton.IsEnabled = true;
                nextButton.IsDefault = true;
                headingLabel.Content = firstPageLabel;
            }
    
            private void finishButton_Click(object sender, RoutedEventArgs e)
            {
                if (ValidateUrl())
                {
                    DialogResult = true;
                    Close();
                }
            }
    
            private bool ValidateUrl()
            {
                string errorMessage;
                if (!PresentationModel.ValidateCurrentUrl(out errorMessage))
                {
                    MessageBox.Show(String.Format("Cannot connect to the SharePoint site: {0}. {1}",
                        PresentationModel.CurrentSiteUrl, errorMessage),
                        "SharePoint Connection Error");
                    return false;
                }
                return true;
            }
        }
    }
    

첫 번째 마법사 페이지 UI를 만들려면

  1. Page1.xaml 파일을 두 번 클릭하여 디자이너에서 사용자 정의 컨트롤을 엽니다.

  2. 디자이너의 XAML 뷰에서 현재 XAML을 다음 XAML로 바꿉니다. 이 XAML에서는 사용자가 디버깅에 사용할 로컬 사이트의 URL을 입력할 수 있는 텍스트 상자와 프로젝트에 샌드박스가 적용되는지 여부를 지정하기 위한 라디오 단추가 포함된 UI를 정의합니다.

    <UserControl x:Class="ProjectTemplateWizard.Page1"
                 xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
                 xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
                 xmlns:mc="https://schemas.openxmlformats.org/markup-compatibility/2006" 
                 xmlns:d="https://schemas.microsoft.com/expression/blend/2008" 
                 mc:Ignorable="d" d:DesignHeight="364" d:DesignWidth="700" Loaded="UserControl_Loaded">
        <Grid Height="364" HorizontalAlignment="Left" Name="page1Grid" Width="700">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="20*" />
                <ColumnDefinition Width="548*" />
                <ColumnDefinition Width="132*" />
            </Grid.ColumnDefinitions>
            <Grid.RowDefinitions>
                <RowDefinition Height="Auto" />
                <RowDefinition Height="Auto" />
                <RowDefinition Height="Auto" />
                <RowDefinition Height="Auto" />
            </Grid.RowDefinitions>
            <TextBox Grid.Row="1" Grid.Column="1" Margin="5,0,1,0" Height="23" Name="siteUrlTextBox" 
                     TextChanged="siteUrlTextBox_TextChanged" />
            <Label Grid.Row="0" Grid.Column="1" Margin="0,20,0,0" Name="siteLabel" FontWeight="Bold" 
                   Target="{Binding ElementName=siteUrlTextBox}" 
                   Content="What local _site do you want to use for debugging?" />
            <Button Grid.Row="1" Grid.Column="2" Content="_Validate" Height="25" Name="validateButton" 
                    Width="88" Click="validateButton_Click" HorizontalAlignment="Left" 
                    Margin="5,0,0,0" VerticalAlignment="Top" />
            <Label Grid.Row="2" Grid.Column="1" Margin="0,10,0,0" Content="What is the trust level for this SharePoint solution?" 
                   Name="trustLabel" FontWeight="Bold" />
            <StackPanel Grid.Row="3" Grid.Column="1" Grid.ColumnSpan="2" Orientation="Vertical">
                <RadioButton Content="Deploy as a sand_boxed solution" Margin="5,0,0,0" Name="sandboxedSolutionRadioButton" 
                             FontWeight="Bold" Checked="sandboxedSolutionRadioButton_Checked" />
                <TextBlock TextWrapping="WrapWithOverflow" Margin="20,7,50,0">Clicking this option causes the solution to be 
                           deployed as a Sandboxed solution. Sandboxed solutions can be deployed by the site collection owner 
                           and are run in a secure, monitored process that has limited resource access.</TextBlock>
                <RadioButton Content="Deploy as a _farm solution" Margin="5,7,0,0" Name="farmSolutionRadioButton" FontWeight="Bold" 
                             Checked="farmSolutionRadioButton_Checked" />
                <TextBlock TextWrapping="WrapWithOverflow" Margin="20,7,50,0">Clicking this option means that users must have 
                           SharePoint administrator privileges to run or deploy the solution.</TextBlock>
            </StackPanel>
        </Grid>
    </UserControl>
    
  3. Visual Basic 프로젝트를 개발하는 중이면 UserControl 요소의 x:Class 특성에 있는 Page1 클래스 이름에서 ProjectTemplateWizard 네임스페이스를 제거합니다. 이 네임스페이스는 XAML의 첫 번째 줄에 있습니다. 지금까지 작업을 마친 후의 첫 번째 줄은 다음과 같습니다.

    <UserControl x:Class="Page1"
    
  4. Page1.xaml 파일의 코드 숨김 파일을 엽니다.

  5. 이 파일의 전체 내용을 다음 코드로 바꿉니다.

    Public Class Page1
        Private mainWindow As WizardWindow
    
        Friend Sub New(ByVal mainWindow As WizardWindow)
            Me.mainWindow = mainWindow
            InitializeComponent()
        End Sub
    
        Private Sub UserControl_Loaded(ByVal sender As Object, ByVal e As RoutedEventArgs)
            If (mainWindow.PresentationModel.IsSandboxed) Then
                sandboxedSolutionRadioButton.IsChecked = True
            Else
                sandboxedSolutionRadioButton.IsEnabled = False
                farmSolutionRadioButton.IsChecked = True
            End If
            siteUrlTextBox.Text = mainWindow.PresentationModel.CurrentSiteUrl
        End Sub
    
        ' Validate that the URL exists on the development computer.
        Private Sub validateButton_Click(ByVal sender As Object, ByVal e As RoutedEventArgs)
            Dim errorMessage As String = String.Empty
            validateButton.IsEnabled = False
    
            If Not mainWindow.PresentationModel.ValidateCurrentUrl(errorMessage) Then
                MessageBox.Show(String.Format("Cannot connect to the SharePoint site: {0}. {1}",
                    mainWindow.PresentationModel.CurrentSiteUrl, errorMessage),
                    "SharePoint Connection Error")
            Else
                MessageBox.Show("Successfully connected to SharePoint site " +
                    mainWindow.PresentationModel.CurrentSiteUrl, "Connection Successful")
            End If
            validateButton.IsEnabled = True
        End Sub
    
        ' Prevent users from finishing the wizard if the URL is not formatted correctly.
        Private Sub siteUrlTextBox_TextChanged(ByVal sender As Object, ByVal e As TextChangedEventArgs)
            Dim url As String = EnsureTrailingSlash(siteUrlTextBox.Text)
    
            ' Perform some basic error-checking on the URL here.
            If url.Length > 0 AndAlso Uri.IsWellFormedUriString(Uri.EscapeUriString(url), UriKind.Absolute) Then
    
                mainWindow.finishButton.IsEnabled = True
                mainWindow.nextButton.IsEnabled = True
                validateButton.IsEnabled = True
                mainWindow.PresentationModel.CurrentSiteUrl = url
                mainWindow.PresentationModel.IsSecondPagePopulated = False
            Else
                mainWindow.finishButton.IsEnabled = False
                mainWindow.nextButton.IsEnabled = False
                validateButton.IsEnabled = False
            End If
        End Sub
    
        Private Sub sandboxedSolutionRadioButton_Checked(ByVal sender As Object, ByVal e As RoutedEventArgs)
            mainWindow.PresentationModel.IsSandboxed = CBool(sandboxedSolutionRadioButton.IsChecked)
        End Sub
    
        Private Sub farmSolutionRadioButton_Checked(ByVal sender As Object, ByVal e As RoutedEventArgs)
            mainWindow.PresentationModel.IsSandboxed = CBool(sandboxedSolutionRadioButton.IsChecked)
        End Sub
    
        Private Function EnsureTrailingSlash(ByVal url As String)
            If Not String.IsNullOrEmpty(url) AndAlso url(url.Length - 1) <> "/" Then
                url += "/"
            End If
            Return url
        End Function
    End Class
    
    using System;
    using System.Windows;
    using System.Windows.Controls;
    
    namespace ProjectTemplateWizard
    {
        public partial class Page1 : UserControl
        {
            private WizardWindow mainWindow;
    
            internal Page1(WizardWindow mainWindow)
            {
                this.mainWindow = mainWindow;
                InitializeComponent();
            }
    
            private void UserControl_Loaded(object sender, RoutedEventArgs e)
            {
                if (mainWindow.PresentationModel.IsSandboxed)
                {
                    sandboxedSolutionRadioButton.IsChecked = true;
                }
                else
                {
                    sandboxedSolutionRadioButton.IsEnabled = false;
                    farmSolutionRadioButton.IsChecked = true;
                }
    
                siteUrlTextBox.Text = mainWindow.PresentationModel.CurrentSiteUrl;
            }
    
            // Validate that the URL exists on the development computer.
            private void validateButton_Click(object sender, RoutedEventArgs e)
            {
                string errorMessage;
                validateButton.IsEnabled = false;
    
                if (!mainWindow.PresentationModel.ValidateCurrentUrl(out errorMessage))
                {
                    MessageBox.Show(String.Format("Cannot connect to the SharePoint site: {0}. {1}",
                        mainWindow.PresentationModel.CurrentSiteUrl, errorMessage),
                        "SharePoint Connection Error");
                }
                else
                {
                    MessageBox.Show("Successfully connected to SharePoint site " +
                        mainWindow.PresentationModel.CurrentSiteUrl, "Connection Successful");
                }
    
                validateButton.IsEnabled = true;
            }
    
            // Prevent users from finishing the wizard if the URL is not formatted correctly.
            private void siteUrlTextBox_TextChanged(object sender, TextChangedEventArgs e)
            {
                string url = EnsureTrailingSlash(siteUrlTextBox.Text);
    
                // Perform some basic error-checking on the URL here.
                if ((url.Length > 0) && (Uri.IsWellFormedUriString(Uri.EscapeUriString(url), UriKind.Absolute)))
                {
                    mainWindow.finishButton.IsEnabled = true;
                    mainWindow.nextButton.IsEnabled = true;
                    validateButton.IsEnabled = true;
                    mainWindow.PresentationModel.CurrentSiteUrl = url;
                    mainWindow.PresentationModel.IsSecondPagePopulated = false;
                }
                else
                {
                    mainWindow.finishButton.IsEnabled = false;
                    mainWindow.nextButton.IsEnabled = false;
                    validateButton.IsEnabled = false;
                }
            }
    
            private void sandboxedSolutionRadioButton_Checked(object sender, RoutedEventArgs e)
            {
                mainWindow.PresentationModel.IsSandboxed = (bool)sandboxedSolutionRadioButton.IsChecked;
            }
    
            private void farmSolutionRadioButton_Checked(object sender, RoutedEventArgs e)
            {
                mainWindow.PresentationModel.IsSandboxed = (bool)sandboxedSolutionRadioButton.IsChecked;
            }
    
            private string EnsureTrailingSlash(string url)
            {
                if (!String.IsNullOrEmpty(url)
                    && url[url.Length - 1] != '/')
                {
                    url += '/';
                }
                return url;
            }
        }
    }
    

두 번째 마법사 페이지 UI를 만들려면

  1. Page2.xaml 파일을 두 번 클릭하여 디자이너에서 사용자 정의 컨트롤을 엽니다.

  2. 디자이너의 XAML 뷰에서 현재 XAML을 다음 XAML로 바꿉니다. 이 XAML에서는 사이트 열의 기본 형식을 선택하기 위한 드롭다운 목록, 갤러리에 사이트 열을 표시할 기본 제공 그룹 또는 사용자 지정 그룹을 지정하기 위한 콤보 상자 및 사이트 열의 이름을 지정하기 위한 텍스트 상자가 포함된 UI를 정의합니다.

    <UserControl x:Class="ProjectTemplateWizard.Page2"
                 xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
                 xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
                 xmlns:mc="https://schemas.openxmlformats.org/markup-compatibility/2006" 
                 xmlns:d="https://schemas.microsoft.com/expression/blend/2008" 
                 mc:Ignorable="d" d:DesignHeight="364" d:DesignWidth="700" Loaded="UserControl_Loaded">
        <Grid Height="364" HorizontalAlignment="Left" Name="page2Grid" Width="700">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="20*" />
                <ColumnDefinition Width="Auto" />
                <ColumnDefinition Width="450*" />
            </Grid.ColumnDefinitions>
            <Grid.RowDefinitions>
                <RowDefinition Height="Auto" />
                <RowDefinition Height="Auto" />
                <RowDefinition Height="Auto" />
            </Grid.RowDefinitions>
            <Label Grid.Row="0" Grid.Column="1" Margin="0,20,0,0" Content="_Type:" Name="fieldTypeLabel" 
                   FontWeight="Bold" Target="{Binding ElementName=fieldTypeComboBox}"/>
            <Label Grid.Row="1" Grid.Column="1" Margin="0,10,0,0" Content="_Group:" Name="groupLabel" 
                   FontWeight="Bold" Target="{Binding ElementName=groupComboBox}"/>
            <Label Grid.Row="2" Grid.Column="1" Margin="0,10,0,0" Content="_Name:" Name="nameLabel" 
                   FontWeight="Bold" Target="{Binding ElementName=nameTextBox}"/>
            <ComboBox Grid.Row="0" Grid.Column="2" HorizontalAlignment="Left" Margin="0,20,0,0" Height="23" 
                      Name="fieldTypeComboBox" Width="450" SelectionChanged="fieldTypeComboBox_SelectionChanged" />
            <ComboBox Grid.Row="1" Grid.Column="2" HorizontalAlignment="Left" Margin="0,10,0,0" Height="23" 
                      Name="groupComboBox" Width="450" IsEditable="True"  />
            <TextBox Grid.Row="2" Grid.Column="2" HorizontalAlignment="Left"  Margin="0,10,0,0" Height="23" 
                     Name="nameTextBox" Width="450" TextChanged="nameTextBox_TextChanged" />
        </Grid>
    </UserControl>
    
  3. Visual Basic 프로젝트를 개발하는 중이면 UserControl 요소의 x:Class 특성에 있는 Page2 클래스 이름에서 ProjectTemplateWizard 네임스페이스를 제거합니다. 이 네임스페이스는 XAML의 첫 번째 줄에 있습니다. 지금까지 작업을 마친 후의 첫 번째 줄은 다음과 같습니다.

    <UserControl x:Class="Page2"
    
  4. Page2.xaml 파일의 코드 숨김 파일을 엽니다.

  5. 이 파일의 전체 내용을 다음 코드로 바꿉니다.

    Public Class Page2
        Private mainWindow As WizardWindow
        Private innerTextBoxForGroupComboBox As TextBox
    
        Friend Sub New(ByVal mainWindow As WizardWindow)
            Me.mainWindow = mainWindow
            InitializeComponent()
        End Sub
    
        Friend Sub ClearControls()
            fieldTypeComboBox.Items.Clear()
            groupComboBox.Items.Clear()
            nameTextBox.Clear()
        End Sub
    
        Friend Sub PopulateSiteColumnOptions()
            ' Add the available field type names to the combo box.
            Dim fieldTypes As System.Collections.ArrayList = mainWindow.PresentationModel.GetFieldTypes()
            If fieldTypes IsNot Nothing Then
                fieldTypes.Sort()
                For Each fieldValue As String In fieldTypes
                    fieldTypeComboBox.Items.Add(fieldValue)
                Next
                fieldTypeComboBox.SelectedIndex = 0
            End If
    
            ' Add the default group names to the combo box.
            Dim fieldGroups As List(Of String) = mainWindow.PresentationModel.GetFieldGroups()
            For Each fieldGroupValue As String In fieldGroups
                groupComboBox.Items.Add(fieldGroupValue)
            Next
            groupComboBox.SelectedIndex = 0
        End Sub
    
        Private Sub UserControl_Loaded(ByVal sender As Object, ByVal e As RoutedEventArgs)
            ' Handle the TextChanged event of the underlying TextBox for the ComboBox. This enables us to determine 
            ' 1) when the user selects an item in the list and 2) when they type their own custom group name. 
            ' The ComboBox.SelectionChanged event is not raised when you type in an editable ComboboBox.
            innerTextBoxForGroupComboBox = CType(groupComboBox.Template.FindName(
                "PART_EditableTextBox", groupComboBox), TextBox)
            AddHandler innerTextBoxForGroupComboBox.TextChanged, AddressOf innerTextBoxForGroupComboBox_TextChanged
        End Sub
    
        Private Sub fieldTypeComboBox_SelectionChanged(ByVal sender As Object, ByVal e As SelectionChangedEventArgs)
            mainWindow.PresentationModel.FieldType = CStr(fieldTypeComboBox.SelectedItem)
        End Sub
    
        Private Sub innerTextBoxForGroupComboBox_TextChanged(ByVal sender As Object, ByVal e As TextChangedEventArgs)
            mainWindow.PresentationModel.FieldGroup = groupComboBox.Text
        End Sub
    
        Private Sub nameTextBox_TextChanged(ByVal sender As Object, ByVal e As TextChangedEventArgs)
            mainWindow.PresentationModel.FieldName = nameTextBox.Text
        End Sub
    End Class
    
    using System.Windows;
    using System.Windows.Controls;
    
    namespace ProjectTemplateWizard
    {
        public partial class Page2 : UserControl
        {
            private WizardWindow mainWindow;
            private TextBox innerTextBoxForGroupComboBox;
    
            internal Page2(WizardWindow mainWindow)
            {
                this.mainWindow = mainWindow;
                InitializeComponent();
            }
    
            internal void ClearControls()
            {
                fieldTypeComboBox.Items.Clear();
                groupComboBox.Items.Clear();
                nameTextBox.Clear();
            }
    
            internal void PopulateSiteColumnOptions()
            {
                // Add the available field type names to the combo box.
                System.Collections.ArrayList fieldTypes = mainWindow.PresentationModel.GetFieldTypes();
                if (fieldTypes != null)
                {
                    fieldTypes.Sort();
                    foreach (string fieldValue in fieldTypes)
                    {
                        fieldTypeComboBox.Items.Add(fieldValue);
                    }
    
                    fieldTypeComboBox.SelectedIndex = 0;
                }
    
                // Add the default group names to the combo box.
                System.Collections.Generic.List<string> fieldGroups = mainWindow.PresentationModel.GetFieldGroups();
                foreach (string fieldGroupValue in fieldGroups)
                {
                    groupComboBox.Items.Add(fieldGroupValue);
                }
    
                groupComboBox.SelectedIndex = 0;
            }
    
            private void UserControl_Loaded(object sender, RoutedEventArgs e)
            {
                // Handle the TextChanged event of the underlying TextBox for the ComboBox. This enables us to determine 
                // 1) when the user selects an item in the list and 2) when they type their own custom group name. 
                // The ComboBox.SelectionChanged event is not raised when you type in an editable ComboboBox.
                innerTextBoxForGroupComboBox = groupComboBox.Template.FindName(
                    "PART_EditableTextBox", groupComboBox) as TextBox;
                innerTextBoxForGroupComboBox.TextChanged += innerTextBoxForGroupComboBox_TextChanged;
            }
    
            private void fieldTypeComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
            {
                mainWindow.PresentationModel.FieldType = (string)fieldTypeComboBox.SelectedItem;
            }
    
            void innerTextBoxForGroupComboBox_TextChanged(object sender, TextChangedEventArgs e)
            {
                mainWindow.PresentationModel.FieldGroup = groupComboBox.Text;
            }
    
            private void nameTextBox_TextChanged(object sender, TextChangedEventArgs e)
            {
                mainWindow.PresentationModel.FieldName = nameTextBox.Text;
            }
        }
    }
    

마법사 구현

IWizard 인터페이스를 구현하여 마법사의 기본 기능을 정의합니다. 이 인터페이스는 마법사가 시작되고 완료될 때와 마법사 실행 중의 특정한 때에 Visual Studio에서 호출하는 메서드를 정의합니다.

마법사를 구현하려면

  1. ProjectTemplateWizard 프로젝트에서 SiteColumnProjectWizard 코드 파일을 엽니다.

  2. 이 파일의 전체 내용을 다음 코드로 바꿉니다.

    Imports EnvDTE
    Imports Microsoft.VisualStudio.SharePoint
    Imports Microsoft.VisualStudio.TemplateWizard
    Imports System
    Imports System.Collections.Generic
    
    Public Class SiteColumnProjectWizard
        Implements IWizard
    
        Private wizardUI As WizardWindow
        Private dteObject As DTE
        Private presentationModel As SiteColumnWizardModel
        Private signingManager As ProjectSigningManager
    
        Public Sub New()
            signingManager = New ProjectSigningManager()
        End Sub
    
        Public Sub RunStarted(ByVal automationObject As Object, ByVal replacementsDictionary As Dictionary(Of String, String), _
            ByVal runKind As WizardRunKind, ByVal customParams() As Object) Implements IWizard.RunStarted
    
            dteObject = CType(automationObject, DTE)
            presentationModel = New SiteColumnWizardModel(dteObject, False)
    
            If Not presentationModel.ProjectService.IsSharePointInstalled Then
                Dim errorString As String = "A SharePoint server is not installed on this computer. A SharePoint server " &
                    "must be installed to work with SharePoint projects."
                System.Windows.MessageBox.Show(errorString, "SharePoint Not Installed", System.Windows.MessageBoxButton.OK,
                    System.Windows.MessageBoxImage.Error)
                Throw New WizardCancelledException(errorString)
            End If
    
            wizardUI = New WizardWindow(presentationModel)
            Dim dialogCompleted? As Boolean = wizardUI.ShowModal()
    
            If (dialogCompleted = True) Then
                replacementsDictionary.Add("$selectedfieldtype$", presentationModel.FieldType)
                replacementsDictionary.Add("$selectedgrouptype$", presentationModel.FieldGroup)
                replacementsDictionary.Add("$fieldname$", presentationModel.FieldName)
                signingManager.GenerateKeyFile()
            Else
                Throw New WizardCancelledException()
            End If
        End Sub
    
        ' Populate the SiteUrl and IsSandboxedSolution properties in the new project, and add a new 
        ' key.snk file to the project.
        Public Sub ProjectFinishedGenerating(ByVal project As Project) _
            Implements IWizard.ProjectFinishedGenerating
            Dim sharePointProject As ISharePointProject = presentationModel.ProjectService.Convert(Of Project, ISharePointProject)(project)
            sharePointProject.SiteUrl = New Uri(presentationModel.CurrentSiteUrl, UriKind.Absolute)
            sharePointProject.IsSandboxedSolution = presentationModel.IsSandboxed
            signingManager.AddKeyFile(project)
        End Sub
    
        ' Always return true; this IWizard implementation throws a WizardCancelledException
        ' that is handled by Visual Studio if the user cancels the wizard.
        Public Function ShouldAddProjectItem(ByVal filePath As String) As Boolean _
            Implements IWizard.ShouldAddProjectItem
            Return True
        End Function
    
        ' The following IWizard methods are not used in this example.
        Public Sub BeforeOpeningFile(ByVal projectItem As ProjectItem) _
            Implements IWizard.BeforeOpeningFile
        End Sub
    
        Public Sub ProjectItemFinishedGenerating(ByVal projectItem As ProjectItem) _
            Implements IWizard.ProjectItemFinishedGenerating
        End Sub
    
        Public Sub RunFinished() Implements IWizard.RunFinished
        End Sub
    End Class
    
    using EnvDTE;
    using Microsoft.VisualStudio.SharePoint;
    using Microsoft.VisualStudio.TemplateWizard;
    using System;
    using System.Collections.Generic;
    
    namespace ProjectTemplateWizard
    {
        public class SiteColumnProjectWizard : IWizard
        {
            private WizardWindow wizardUI;
            private DTE dteObject;
            private SiteColumnWizardModel presentationModel;
            private ProjectSigningManager signingManager;
    
            public SiteColumnProjectWizard()
            {
                signingManager = new ProjectSigningManager();
            }
    
            public void RunStarted(object automationObject, Dictionary<string, string> replacementsDictionary, 
                WizardRunKind runKind, object[] customParams)
            {
                dteObject = automationObject as DTE;
                presentationModel = new SiteColumnWizardModel(dteObject, false);
    
                if (!presentationModel.ProjectService.IsSharePointInstalled)
                {
                    string errorString = "A SharePoint server is not installed on this computer. A SharePoint server " +
                        "must be installed to work with SharePoint projects.";
                    System.Windows.MessageBox.Show(errorString, "SharePoint Not Installed", System.Windows.MessageBoxButton.OK,
                        System.Windows.MessageBoxImage.Error);
                    throw new WizardCancelledException(errorString);
                }
    
                wizardUI = new WizardWindow(presentationModel);
                Nullable<bool> dialogCompleted = wizardUI.ShowModal();
    
                if (dialogCompleted == true)
                {
                    replacementsDictionary.Add("$selectedfieldtype$", presentationModel.FieldType);
                    replacementsDictionary.Add("$selectedgrouptype$", presentationModel.FieldGroup);
                    replacementsDictionary.Add("$fieldname$", presentationModel.FieldName);
                    signingManager.GenerateKeyFile();
                }
                else
                {
                    throw new WizardCancelledException();
                }
            }
    
            // Populate the SiteUrl and IsSandboxedSolution properties in the new project, and add a new 
            // key.snk file to the project.
            public void ProjectFinishedGenerating(Project project)
            {
                ISharePointProject sharePointProject = presentationModel.ProjectService.Convert<Project, ISharePointProject>(project);
                sharePointProject.SiteUrl = new Uri(presentationModel.CurrentSiteUrl, UriKind.Absolute);
                sharePointProject.IsSandboxedSolution = presentationModel.IsSandboxed;
                signingManager.AddKeyFile(project);
            }
    
            // Always return true; this IWizard implementation throws a WizardCancelledException
            // that is handled by Visual Studio if the user cancels the wizard.
            public bool ShouldAddProjectItem(string filePath)
            {
                return true;
            }
    
            // The following IWizard methods are not used in this example.
            public void BeforeOpeningFile(ProjectItem projectItem)
            {
            }
    
            public void ProjectItemFinishedGenerating(ProjectItem projectItem)
            {
            }
    
            public void RunFinished()
            {
            }
        }
    }
    

SharePoint 명령 만들기

SharePoint 서버 개체 모델을 호출하는 두 개의 사용자 지정 명령을 만듭니다. 한 명령은 사용자가 마법사에서 입력하는 사이트 URL이 유효한지 여부를 확인합니다. 다른 명령은 사용자가 새 사이트 열의 기반으로 사용할 필드 형식을 선택할 수 있도록 지정된 SharePoint 사이트에서 모든 필드 형식을 가져옵니다.

SharePoint 명령을 정의하려면

  1. SharePointCommands 프로젝트에서 Commands 코드 파일을 엽니다.

  2. 이 파일의 전체 내용을 다음 코드로 바꿉니다.

    Imports Microsoft.SharePoint
    Imports Microsoft.VisualStudio.SharePoint.Commands
    
    Namespace Contoso.SharePoint.Commands
    
        Friend Class Commands
    
            <SharePointCommand(CommandIds.ValidateSite)> _
            Private Function ValidateSite(ByVal context As ISharePointCommandContext, ByVal url As Uri) As Boolean
                Using site As SPSite = New SPSite(url.AbsoluteUri)
                    Dim webUrl As String = DetermineWebUrl(url.AbsolutePath, site.ServerRelativeUrl)
                    If webUrl IsNot Nothing Then
                        Using web As SPWeb = site.OpenWeb(webUrl, True)
                            Return web.Exists
                        End Using
                    End If
                End Using
                Return False
            End Function
    
            ' For simplicity, this command does not check to make sure the provided Uri is valid. 
            ' Use the ValidateSite command to verify that the Uri is valid first.
            <SharePointCommand(CommandIds.GetFieldTypes)> _
            Private Function GetFieldTypes(ByVal context As ISharePointCommandContext, ByVal url As Uri) As String()
                Dim columnDefinitions As List(Of String) = New List(Of String)()
                Using site As SPSite = New SPSite(url.AbsoluteUri)
                    Dim webUrl As String = DetermineWebUrl(url.AbsolutePath, site.ServerRelativeUrl)
                    Using web As SPWeb = site.OpenWeb(webUrl, True)
                        For Each columnDefinition As SPFieldTypeDefinition In web.FieldTypeDefinitionCollection
                            columnDefinitions.Add(columnDefinition.TypeName)
                        Next
                        ' SharePoint commands cannot serialize List<string>, so return an array.
                        Return columnDefinitions.ToArray()
                    End Using
                End Using
            End Function
    
            Private Function DetermineWebUrl(ByVal serverRelativeInputUrl As String, ByVal serverRelativeSiteUrl As String) As String
                ' Make sure both URLs have a trailing slash.
                serverRelativeInputUrl = EnsureTrailingSlash(serverRelativeInputUrl)
                serverRelativeSiteUrl = EnsureTrailingSlash(serverRelativeSiteUrl)
    
                Dim webUrl As String = Nothing
                Dim isSubString As Boolean = serverRelativeInputUrl.StartsWith(serverRelativeSiteUrl, StringComparison.OrdinalIgnoreCase)
    
                If isSubString Then
                    ' The Web URL cannot have escaped characters.
                    webUrl = Uri.UnescapeDataString(serverRelativeInputUrl.Substring(serverRelativeSiteUrl.Length))
                End If
                Return webUrl
            End Function
    
            Private Function EnsureTrailingSlash(ByVal url As String)
                If Not String.IsNullOrEmpty(url) AndAlso url(url.Length - 1) <> "/" Then
                    url += "/"
                End If
                Return url
            End Function
        End Class
    End Namespace
    
    using System;
    using System.Collections.Generic;
    using Microsoft.SharePoint;
    using Microsoft.VisualStudio.SharePoint.Commands;
    
    namespace Contoso.SharePoint.Commands
    {
        internal class Commands
        {
            [SharePointCommand(CommandIds.ValidateSite)]
            private bool ValidateSite(ISharePointCommandContext context, Uri url)
            {
                using (SPSite site = new SPSite(url.AbsoluteUri))
                {
                    string webUrl = DetermineWebUrl(url.AbsolutePath, site.ServerRelativeUrl);
                    if (webUrl != null)
                    {
                        using (SPWeb web = site.OpenWeb(webUrl, true))
                        {
                            return web.Exists;
                        }
                    }
                }
    
                return false;
            }
    
            // For simplicity, this command does not check to make sure the provided Uri is valid. 
            // Use the ValidateSite command to verify that the Uri is valid first.
            [SharePointCommand(CommandIds.GetFieldTypes)]
            private string[] GetFieldTypes(ISharePointCommandContext context, Uri url)
            {
                List<string> columnDefinitions = new List<string>();
                using (SPSite site = new SPSite(url.AbsoluteUri))
                {
                    string webUrl = DetermineWebUrl(url.AbsolutePath, site.ServerRelativeUrl);
                    using (SPWeb web = site.OpenWeb(webUrl, true))
                    {
                        foreach (SPFieldTypeDefinition columnDefinition in web.FieldTypeDefinitionCollection)
                        {
                            columnDefinitions.Add(columnDefinition.TypeName);
                        }
    
                        // SharePoint commands cannot serialize List<string>, so return an array.
                        return columnDefinitions.ToArray();
                    }
                }
            }
    
            private string DetermineWebUrl(string serverRelativeInputUrl, string serverRelativeSiteUrl)
            {
                // Make sure both URLs have a trailing slash.
                serverRelativeInputUrl = EnsureTrailingSlash(serverRelativeInputUrl);
                serverRelativeSiteUrl = EnsureTrailingSlash(serverRelativeSiteUrl);
    
                string webUrl = null;
                bool isSubString = serverRelativeInputUrl.StartsWith(serverRelativeSiteUrl, StringComparison.OrdinalIgnoreCase);
    
                if (isSubString)
                {
                    // The Web URL cannot have escaped characters.
                    webUrl = Uri.UnescapeDataString(serverRelativeInputUrl.Substring(serverRelativeSiteUrl.Length));
                }
    
                return webUrl;
            }
    
            private string EnsureTrailingSlash(string url)
            {
                if (!String.IsNullOrEmpty(url)
                    && url[url.Length - 1] != '/')
                {
                    url += '/';
                }
                return url;
            }
        }
    }
    

검사점

이 연습의 이전 단계를 통해 마법사를 위한 모든 코드가 프로젝트에 포함되었습니다. 프로젝트를 빌드하여 오류 없이 컴파일되는지 확인합니다.

프로젝트를 빌드하려면

  • 빌드 메뉴에서 솔루션 빌드를 선택합니다.

프로젝트 템플릿에서 key.snk 파일 제거

연습: 프로젝트 템플릿을 사용하여 사이트 열 프로젝트 항목 만들기, 1부에서 만든 프로젝트 템플릿에는 각 사이트 열 프로젝트 인스턴스에 서명하는 데 사용되는 key.snk 파일이 포함되어 있습니다. 이제 마법사에서 각 프로젝트에 대한 새 key.snk 파일을 생성하기 때문에 이 key.snk 파일은 더 이상 필요하지 않습니다. 프로젝트 템플릿에서 key.snk 파일을 제거하고 이 파일에 대한 참조를 제거합니다.

프로젝트 템플릿에서 key.snk 파일을 제거하려면

  1. 솔루션 탐색기SiteColumnProjectTemplate 노드 아래에서 key.snk 파일을 마우스 오른쪽 단추로 클릭하고 삭제를 클릭합니다. 삭제 확인을 요청하는 메시지 상자에서 확인을 클릭합니다.

  2. SiteColumnProjectTemplate 노드에서 SiteColumnProjectTemplate.vstemplate 파일을 엽니다.

  3. 파일에서 다음 요소를 제거합니다.

    <ProjectItem ReplaceParameters="false" TargetFileName="key.snk">key.snk</ProjectItem>
    
  4. 파일을 저장한 후 닫습니다.

  5. SiteColumnProjectTemplate 노드에서 ProjectTemplate.csproj 또는 ProjectTemplate.vbproj 파일을 엽니다.

  6. 다음 PropertyGroup 요소를 제거합니다.

    <PropertyGroup>
      <SignAssembly>true</SignAssembly>
      <AssemblyOriginatorKeyFile>key.snk</AssemblyOriginatorKeyFile>
    </PropertyGroup>
    
  7. 다음 None 요소를 제거합니다.

    <None Include="key.snk" />
    
  8. 파일을 저장한 후 닫습니다.

마법사를 프로젝트 템플릿과 연결

마법사를 구현했으므로 이 마법사를 사이트 열 프로젝트 템플릿과 연결해야 합니다. 이렇게 하려면 세 가지 절차를 완료해야 합니다.

  1. 강력한 이름으로 마법사 어셈블리에 서명합니다.

  2. 마법사 어셈블리의 공개 키 토큰을 가져옵니다.

  3. 사이트 열 프로젝트 템플릿의 .vstemplate 파일에서 마법사 어셈블리에 대한 참조를 추가합니다.

강력한 이름으로 마법사 어셈블리에 서명하려면

  1. 솔루션 탐색기에서 ProjectTemplateWizard 프로젝트 노드를 마우스 오른쪽 단추로 클릭하고 속성을 클릭합니다.

  2. 서명 탭을 클릭합니다.

  3. 어셈블리 서명 확인란을 선택합니다.

  4. 강력한 이름 키 파일 선택 드롭다운 목록에서 **<새로 만들기...>**를 선택합니다.

  5. 강력한 이름 키 만들기 대화 상자에서 새 키 파일의 이름을 입력하고 암호로 내 키 파일 보호 확인란의 선택을 취소합니다.

  6. 확인을 클릭합니다.

  7. 빌드 메뉴에서 솔루션 빌드를 선택합니다.

마법사 어셈블리의 공개 키 토큰을 가져오려면

  1. Visual Studio 명령 프롬프트 창을 엽니다.

  2. 다음 명령을 실행합니다. path to wizard assembly를 개발 컴퓨터에서 ProjectTemplateWizard 프로젝트를 위해 빌드된 ProjectTemplateWizard.dll 어셈블리의 전체 경로로 바꿉니다.

    sn.exe -T path to wizard assembly
    

    ProjectTemplateWizard.dll 어셈블리의 공개 키 토큰은 Visual Studio 명령 프롬프트 창에 기록됩니다.

  3. Visual Studio 명령 프롬프트 창을 계속 열어 둡니다. 공개 키 토큰은 다음 절차 중에 필요합니다.

마법사 어셈블리에 대한 참조를 .vstemplate 파일에 추가하려면

  1. 솔루션 탐색기에서 SiteColumnProjectTemplate 프로젝트 노드를 확장하고 SiteColumnProjectTemplate.vstemplate 파일을 엽니다.

  2. 파일의 끝 부분에서 </TemplateContent> 및 </VSTemplate> 태그 사이에 다음 WizardExtension 요소를 추가합니다. PublicKeyToken 특성의 your token 값을 이전 절차에서 가져온 공개 키 토큰으로 바꿉니다.

    <WizardExtension>
      <Assembly>ProjectTemplateWizard, Version=1.0.0.0, Culture=neutral, PublicKeyToken=your token</Assembly>
      <FullClassName>ProjectTemplateWizard.SiteColumnProjectWizard</FullClassName>
    </WizardExtension>
    

    WizardExtension 요소에 대한 자세한 내용은 WizardExtension 요소(Visual Studio 템플릿)를 참조하십시오.

  3. 파일을 저장한 후 닫습니다.

프로젝트 템플릿의 Elements.xml 파일에 대체 가능한 매개 변수 추가

대체 가능한 매개 변수 여러 개를 SiteColumnProjectTemplate 프로젝트의 Elements.xml 파일에 추가합니다. 이러한 매개 변수는 앞에서 정의한 SiteColumnProjectWizard 클래스의 RunStarted 메서드에서 초기화됩니다. 사용자가 사이트 열 프로젝트를 만들면 Visual Studio에서는 자동으로 새 프로젝트의 Elements.xml 파일에 있는 이러한 매개 변수를 마법사에서 지정한 값으로 바꿉니다.

대체 가능한 매개 변수는 달러 기호($) 문자로 시작하고 끝나는 토큰입니다. 대체 가능한 매개 변수를 직접 정의할 뿐 아니라 SharePoint 프로젝트 시스템에 의해 정의되고 초기화되는 기본 제공 매개 변수를 사용할 수도 있습니다. 자세한 내용은 대체 가능 매개 변수를 참조하십시오.

대체 가능한 매개 변수를 Elements.xml 파일에 추가하려면

  1. SiteColumnProjectTemplate 프로젝트에서 Elements.xml 파일을 엽니다.

  2. 이 파일의 내용을 다음 XML로 바꿉니다.

    <?xml version="1.0" encoding="utf-8"?>
    <Elements xmlns="https://schemas.microsoft.com/sharepoint/">
      <Field ID="{$guid5$}" 
             Name="$fieldname$" 
             DisplayName="$fieldname$" 
             Type="$selectedfieldtype$" 
             Group="$selectedgrouptype$">
      </Field>
    </Elements>
    

    새로운 XML에서는 Name, DisplayName, Type 및 Group 특성의 값을 대체 가능한 사용자 지정 매개 변수로 변경합니다.

  3. 파일을 저장한 후 닫습니다.

VSIX 패키지에 마법사 추가

사이트 열 프로젝트 템플릿이 포함된 VSIX 패키지를 사용하여 마법사를 배포하려면 마법사 프로젝트와 SharePoint 명령 프로젝트에 대한 참조를 VSIX 프로젝트의 source.extension.vsixmanifest 파일에 추가합니다.

VSIX 패키지에 마법사를 추가하려면

  1. 솔루션 탐색기에서 SiteColumnProjectItem 프로젝트의 source.extension.vsixmanifest 파일을 두 번 클릭합니다.

    매니페스트 편집기에서 파일이 열립니다.

  2. 편집기의 콘텐츠 섹션에서 콘텐츠 추가 단추를 클릭합니다.

  3. 콘텐츠 추가 대화 상자의 콘텐츠 형식을 선택하십시오. 목록 상자에서 템플릿 마법사를 선택합니다.

  4. 소스 선택에서 프로젝트 라디오 단추를 클릭하고 그 옆에 있는 목록 상자에서 ProjectTemplateWizard를 선택합니다.

  5. 확인을 클릭합니다.

  6. 매니페스트 편집기에서 콘텐츠 추가 단추를 다시 클릭합니다.

  7. 콘텐츠 추가 대화 상자의 콘텐츠 형식을 선택하십시오. 목록 상자에서 사용자 지정 확장 형식을 선택합니다.

    참고

    이 값은 extension.vsixmanifest 파일의 CustomExtension 요소에 해당합니다. 이 요소는 Visual Studio 확장에 포함할 사용자 지정 확장을 지정합니다. 자세한 내용은 CustomExtension Element (VSX Schema)를 참조하십시오.

  8. 형식 텍스트 상자에 SharePoint.Commands.v4를 입력합니다.

    참고

    이 값은 extension.vsixmanifest 파일에 있는 CustomExtension 요소의 Type 특성에 해당합니다. 사용자 지정 SharePoint 명령이 포함된 모든 사용자 지정 확장 어셈블리에 값 Sharepoint.Commands.v4가 필요합니다.

  9. 소스 선택에서 프로젝트 라디오 단추를 클릭하고 그 옆에 있는 목록 상자에서 SharePointCommands를 선택합니다.

  10. 확인을 클릭합니다.

  11. 빌드 메뉴에서 솔루션 빌드를 클릭합니다. 솔루션이 오류 없이 컴파일되는지 확인합니다.

마법사 테스트

이제 마법사를 테스트할 준비가 되었습니다. 우선 실험 모드의 Visual Studio 인스턴스에서 SiteColumnProjectItem 솔루션 디버깅을 시작합니다. 그런 다음 실험 모드의 Visual Studio 인스턴스에서 사이트 열 프로젝트에 대한 마법사를 테스트합니다. 마지막으로, 프로젝트를 빌드 및 실행하여 사이트 열이 예상대로 작동하는지 확인합니다.

솔루션 디버깅을 시작하려면

  1. 관리자 권한으로 Visual Studio를 다시 시작하고 SiteColumnProjectItem 솔루션을 엽니다.

  2. ProjectTemplateWizard 프로젝트에서 SiteColumnProjectWizard 코드 파일을 열고 RunStarted 메서드의 코드 첫 줄에 중단점을 추가합니다.

  3. 디버그 메뉴에서 예외를 클릭합니다.

  4. 예외 대화 상자에서 Common Language Runtime Exceptions에 대해 Throw됨사용자가 처리하지 않음 확인란의 선택이 취소되어 있는지 확인합니다.

  5. 확인을 클릭합니다.

  6. F5 키를 눌러 디버깅을 시작합니다.

    Visual Studio에서는 확장을 %UserProfile%\AppData\Local\Microsoft\VisualStudio\10.0Exp\Extensions\Contoso\Site Column\1.0에 설치하고 실험 모드의 Visual Studio 인스턴스를 시작합니다. 이 Visual Studio 인스턴스에서 프로젝트 항목을 테스트합니다.

Visual Studio에서 마법사를 테스트하려면

  1. 실험 모드의 Visual Studio 인스턴스에서 파일 메뉴의 새로 만들기를 가리킨 다음 프로젝트를 클릭합니다.

  2. 프로젝트 템플릿에서 지원하는 언어에 따라 Visual C# 또는 Visual Basic을 확장하고 SharePoint를 확장한 다음 2010을 클릭합니다.

  3. 프로젝트 템플릿 목록에서 사이트 열을 클릭합니다.

  4. 이름 상자에 SiteColumnWizardTest를 입력합니다.

  5. 확인을 클릭합니다.

  6. 다른 Visual Studio 인스턴스의 코드가 이전에 RunStarted 메서드에 설정한 중단점에서 중지하는지 확인합니다. F5 키를 눌러 프로젝트를 계속 디버깅합니다.

  7. SharePoint 사용자 지정 마법사에서 디버깅에 사용할 사이트의 URL을 입력하고 다음을 클릭합니다.

  8. SharePoint 사용자 지정 마법사의 두 번째 페이지에서 다음과 같이 선택합니다.

    • 형식에 대한 드롭다운 목록에서 부울을 선택합니다.

    • 그룹에 대한 콤보 상자에 사용자 지정 예/아니요 열을 입력합니다.

    • 이름에 대한 텍스트 상자에 내 예/아니요 열을 입력합니다.

  9. 마침을 클릭합니다.

    새 프로젝트가 Field1이라는 프로젝트 항목과 함께 솔루션 탐색기에 나타나고 Elements.xml 파일이 편집기에서 열립니다. 마법사에서 지정한 값이 Elements.xml에 포함되어 있는지 확인합니다.

SharePoint에서 사이트 열을 테스트하려면

  1. 실험 모드의 Visual Studio 인스턴스에서 F5 키를 누릅니다. 사이트 열이 패키지되어 프로젝트의 사이트 URL 속성에 지정된 SharePoint 사이트로 배포됩니다. 이 사이트의 기본 페이지가 웹 브라우저에서 열립니다.

    참고

    스크립트 디버깅 사용 안 함 대화 상자가 표시되면 를 클릭하여 프로젝트를 계속 디버깅합니다.

  2. 사이트 동작 메뉴에서 사이트 설정을 클릭합니다.

  3. 갤러리에서 사이트 열을 클릭합니다.

  4. 사이트 열의 목록에서 내 예/아니요 열이라는 열이 포함된 사용자 지정 예/아니요 열 그룹이 있는지 확인합니다.

  5. 웹 브라우저를 닫습니다.

개발 컴퓨터 정리

프로젝트 항목의 테스트를 마쳤으면 실험 모드의 Visual Studio 인스턴스에서 프로젝트 템플릿을 제거합니다.

개발 컴퓨터를 정리하려면

  1. 실험 모드의 Visual Studio 인스턴스에서 도구 메뉴의 확장 관리자를 클릭합니다.

    확장 관리자 대화 상자가 열립니다.

  2. 확장 목록에서 사이트 열을 클릭한 다음 제거를 클릭합니다.

  3. 나타나는 대화 상자에서 를 클릭하여 확장을 제거합니다.

  4. 지금 다시 시작을 클릭하여 제거를 완료합니다.

  5. Visual Studio의 두 인스턴스, 즉 실험 모드의 인스턴스와 SiteColumnProjectItem 솔루션이 열려 있는 Visual Studio 인스턴스를 모두 닫습니다.

참고 항목

작업

방법: 프로젝트 템플릿에 마법사 사용

참조

Visual Studio 템플릿 스키마 참조

IWizard

기타 리소스

연습: 프로젝트 템플릿을 사용하여 사이트 열 프로젝트 항목 만들기, 1부

사용자 지정 SharePoint 프로젝트 항목 형식 정의

SharePoint 프로젝트 항목에 대한 항목 템플릿 및 프로젝트 템플릿 만들기