사용자에 역할 할당(C#)

작성자 : Scott Mitchell

참고

이 문서가 작성된 이후 ASP.NET 멤버 자격 공급자는 ASP.NET ID로 대체되었습니다. 이 문서를 작성할 때 추천하는 멤버 자격 공급자가 아닌 ASP.NET ID 플랫폼을 사용하도록 앱을 업데이트하는 것이 좋습니다. ASP.NET ID는 를 포함하여 ASP.NET 멤버 자격 시스템에 비해 여러 가지 이점이 있습니다.

  • 성능 향상
  • 향상된 확장성 및 테스트 용이성
  • OAuth, OpenID Connect 및 2단계 인증 지원
  • 클레임 기반 ID 지원
  • ASP.Net Core와의 상호 운용성 향상

코드 다운로드 또는 PDF 다운로드

이 자습서에서는 사용자가 어떤 역할에 속하는지 관리하는 데 도움이 되도록 두 개의 ASP.NET 페이지를 빌드합니다. 첫 번째 페이지에는 지정된 역할에 속한 사용자, 특정 사용자가 속한 역할, 특정 역할에서 특정 사용자를 할당하거나 제거하는 기능을 볼 수 있는 기능이 포함됩니다. 두 번째 페이지에서는 새로 만든 사용자가 속한 역할을 지정하는 단계를 포함하도록 CreateUserWizard 컨트롤을 보강합니다. 이는 관리자가 새 사용자 계정을 만들 수 있는 시나리오에서 유용합니다.

소개

이전 자습서에서는 역할 프레임워크와 를 검사했습니다SqlRoleProvider. 클래스를 사용하여 Roles 역할을 만들고, 검색하고, 삭제하는 방법을 알아보았습니다. 역할을 만들고 삭제하는 것 외에도 역할에서 사용자를 할당하거나 제거할 수 있어야 합니다. 아쉽게도 ASP.NET 사용자가 어떤 역할에 속하는지 관리하기 위한 웹 컨트롤과 함께 제공되지 않습니다. 대신 이러한 연결을 관리하기 위해 자체 ASP.NET 페이지를 만들어야 합니다. 좋은 소식은 역할에 사용자를 추가하고 제거하는 것이 매우 쉽다는 것입니다. 클래스에는 Roles 하나 이상의 역할에 하나 이상의 사용자를 추가하는 여러 메서드가 포함되어 있습니다.

이 자습서에서는 사용자가 어떤 역할에 속하는지 관리하는 데 도움이 되도록 두 개의 ASP.NET 페이지를 빌드합니다. 첫 번째 페이지에는 지정된 역할에 속한 사용자, 특정 사용자가 속한 역할, 특정 역할에서 특정 사용자를 할당하거나 제거하는 기능을 볼 수 있는 기능이 포함됩니다. 두 번째 페이지에서는 새로 만든 사용자가 속한 역할을 지정하는 단계를 포함하도록 CreateUserWizard 컨트롤을 보강합니다. 이는 관리자가 새 사용자 계정을 만들 수 있는 시나리오에서 유용합니다.

그럼 시작하겠습니다.

사용자가 어떤 역할에 속하는지 나열

이 자습서의 첫 번째 비즈니스 순서는 사용자를 역할에 할당할 수 있는 웹 페이지를 만드는 것입니다. 역할에 사용자를 할당하는 방법을 고민하기 전에 먼저 어떤 사용자가 어떤 역할에 속하는지 결정하는 방법에 집중해 보겠습니다. 이 정보를 표시하는 방법에는 두 가지가 있습니다. "역할별" 또는 "사용자별". 방문자가 역할을 선택한 다음 역할에 속한 모든 사용자("역할별" 표시)를 표시하도록 허용하거나 방문자에게 사용자를 선택한 다음 해당 사용자에게 할당된 역할("사용자별" 표시)을 표시하라는 메시지를 표시할 수 있습니다.

"역할별" 보기는 방문자가 특정 역할에 속한 사용자 집합을 알고 싶어하는 경우에 유용합니다. 방문자가 특정 사용자의 역할을 알아야 하는 경우 "사용자별" 보기가 이상적입니다. 페이지에 "역할별" 인터페이스와 "사용자별" 인터페이스가 모두 포함되도록 합시다.

"사용자별" 인터페이스 만들기부터 시작합니다. 이 인터페이스는 드롭다운 목록과 확인란 목록으로 구성됩니다. 드롭다운 목록은 시스템의 사용자 집합으로 채워집니다. 확인란이 역할을 열거합니다. 드롭다운 목록에서 사용자를 선택하면 사용자가 속한 역할이 검사. 그러면 페이지를 방문하는 사용자가 확인란을 검사 선택 취소하여 해당 역할에서 선택한 사용자를 추가하거나 제거할 수 있습니다.

참고

드롭다운 목록을 사용하여 사용자 계정을 나열하는 것은 수백 개의 사용자 계정이 있을 수 있는 웹 사이트에 이상적인 선택이 아닙니다. 드롭다운 목록은 사용자가 비교적 짧은 옵션 목록에서 하나의 항목을 선택할 수 있도록 설계되었습니다. 목록 항목 수가 증가함에 따라 빠르게 다루기 어려워집니다. 잠재적으로 많은 수의 사용자 계정이 있는 웹 사이트를 빌드하는 경우 페이지 지정 가능한 GridView 또는 방문자에게 편지를 선택하라는 메시지를 표시하는 필터링 가능한 인터페이스와 같은 대체 사용자 인터페이스를 사용하는 것이 좋습니다. 그러면 사용자 이름이 선택한 문자로 시작하는 사용자만 표시됩니다.

1단계: "사용자별" 사용자 인터페이스 빌드

UsersAndRoles.aspx 페이지를 엽니다. 페이지 맨 위에 라는 ActionStatus 레이블 웹 컨트롤을 추가하고 해당 Text 속성을 지웁합니다. 이 레이블을 사용하여 수행된 작업에 대한 피드백을 제공하여 "사용자 Tito가 관리자 역할에 추가되었습니다." 또는 "사용자 지선이 감독자 역할에서 제거되었습니다."와 같은 메시지를 표시합니다. 이러한 메시지를 돋보이게 하려면 Label의 CssClass 속성을 "중요"로 설정합니다.

<p align="center"> 

     <asp:Label ID="ActionStatus" runat="server" CssClass="Important"></asp:Label> 
</p>

다음으로 스타일시트에 다음 CSS 클래스 정의를 Styles.css 추가합니다.

.Important 
{ 
     font-size: large; 
     color: Red; 
}

이 CSS 정의는 큰 빨간색 글꼴을 사용하여 레이블을 표시하도록 브라우저에 지시합니다. 그림 1은 Visual Studio Designer 통해 이 효과를 보여줍니다.

레이블의 CssClass 속성으로 인해 큰 빨간색 글꼴이 생성됩니다.

그림 1: 레이블의 CssClass 속성 결과 큰 빨간색 글꼴(전체 크기 이미지를 보려면 클릭)

그런 다음 페이지에 DropDownList를 추가하고, 해당 ID 속성을 UserList로 설정하고, 해당 AutoPostBack 속성을 True로 설정합니다. 이 DropDownList를 사용하여 시스템의 모든 사용자를 나열합니다. 이 DropDownList는 MembershipUser 개체의 컬렉션에 바인딩됩니다. DropDownList에서 MembershipUser 개체의 UserName 속성을 표시하고 목록 항목의 값으로 사용하려면 DropDownList 및 DataTextFieldDataValueField 속성을 "UserName"으로 설정합니다.

DropDownList 아래에 라는 UsersRoleList리피터를 추가합니다. 이 반복기는 시스템의 모든 역할을 일련의 확인란으로 나열합니다. 다음 선언적 태그를 사용하여 Repeater를 ItemTemplate 정의합니다.

<asp:Repeater ID="UsersRoleList" runat="server"> 
     <ItemTemplate> 
          <asp:CheckBox runat="server" ID="RoleCheckBox" AutoPostBack="true" 

               Text='<%# Container.DataItem %>' /> 
          <br /> 
     </ItemTemplate> 
</asp:Repeater>

태그에는 ItemTemplate 라는 단일 CheckBox 웹 컨트롤이 RoleCheckBox포함됩니다. CheckBox의 AutoPostBack 속성은 True로 설정되고 속성은 에 TextContainer.DataItem바인딩됩니다. 데이터 바인딩 구문의 이유는 Roles Container.DataItem 프레임워크가 역할 이름 목록을 문자열 배열로 반환하기 때문이며, 이 문자열 배열은 Repeater에 바인딩하기 때문입니다. 이 구문을 사용하여 데이터 웹 컨트롤에 바인딩된 배열의 내용을 표시하는 이유에 대한 자세한 설명은 이 자습서의 scope. 이 문제에 대한 자세한 내용은 스칼라 배열을 데이터 웹 컨트롤에 바인딩을 참조하세요.

이 시점에서 "사용자별" 인터페이스의 선언적 태그는 다음과 유사하게 표시됩니다.

<h3>Manage Roles By User</h3> 

<p> 
     <b>Select a User:</b> 
     <asp:DropDownList ID="UserList" runat="server" AutoPostBack="True" 
          DataTextField="UserName" DataValueField="UserName"> 

     </asp:DropDownList> 
</p> 
<p> 
     <asp:Repeater ID="UsersRoleList" runat="server"> 
          <ItemTemplate> 
               <asp:CheckBox runat="server" ID="RoleCheckBox" AutoPostBack="true" 

                    Text='<%# Container.DataItem %>' /> 
               <br /> 
          </ItemTemplate> 
     </asp:Repeater> 
</p>

이제 사용자 계정 집합을 DropDownList에 바인딩하고 역할 집합을 Repeater에 바인딩하는 코드를 작성할 준비가 되었습니다. 페이지의 코드 숨김 클래스에서 다음 코드를 사용하여 라는 메서드와 라는 BindUsersToUserListBindRolesList메서드를 추가합니다.

private void BindUsersToUserList() 
{ 
     // Get all of the user accounts 
     MembershipUserCollection users = Membership.GetAllUsers(); 
     UserList.DataSource = users; 
     UserList.DataBind(); 
}
 
private void BindRolesToList() 
{ 
     // Get all of the roles 
     string[] roles = Roles.GetAllRoles(); 
     UsersRoleList.DataSource = roles; 
     UsersRoleList.DataBind(); 
}

메서드는 BindUsersToUserList 메서드를 통해 Membership.GetAllUsers시스템의 모든 사용자 계정을 검색합니다. 그러면 인스턴스의 MembershipUserCollectionMembershipUser컬렉션인 개체가 반환됩니다. 그런 다음 이 컬렉션은 DropDownList에 UserList 바인딩됩니다. 컬렉션을 구성하는 인스턴스에는 MembershipUser , , Email및 와 IsOnline같은 UserName다양한 속성이 CreationDate포함됩니다. DropDownList에 속성 값을 UserName 표시하도록 지시하려면 DropDownList의 DataTextFieldDataValueField 속성이 UserList "UserName"으로 설정되었는지 확인합니다.

참고

메서드에는 Membership.GetAllUsers 입력 매개 변수를 허용하지 않고 모든 사용자를 반환하는 오버로드와 페이지 인덱스 및 페이지 크기에 대한 정수 값을 사용하고 지정된 사용자 하위 집합만 반환하는 오버로드가 있습니다. 페이저블 사용자 인터페이스 요소에 많은 양의 사용자 계정이 표시되는 경우 두 번째 오버로드는 사용자 계정의 정확한 하위 집합만 반환하므로 사용자를 보다 효율적으로 페이딩하는 데 사용할 수 있습니다.

메서드는 BindRolesToList 시스템의 역할이 포함된 문자열 배열을 반환하는 클래스의 GetAllRoles 메서드를 호출 Roles 하여 시작합니다. 그런 다음 이 문자열 배열은 Repeater에 바인딩됩니다.

마지막으로 페이지가 처음 로드될 때 이러한 두 메서드를 호출해야 합니다. 다음 코드를 Page_Load 이벤트 처리기에 추가합니다.

protected void Page_Load(object sender, EventArgs e) 
{ 
     if (!Page.IsPostBack) 
     { 
          // Bind the users and roles 
          BindUsersToUserList(); 
          BindRolesToList(); 
     } 
}

이 코드를 사용하면 잠시 브라우저를 통해 페이지를 방문하세요. 화면은 그림 2와 유사해야 합니다. 모든 사용자 계정이 드롭다운 목록에 채워지고 그 아래에 각 역할이 확인란으로 표시됩니다. DropDownList 및 CheckBoxes의 속성을 True로 설정 AutoPostBack 했으므로 선택한 사용자를 변경하거나 역할을 선택하거나 선택 취소하면 포스트백이 발생합니다. 그러나 이러한 작업을 처리하는 코드를 아직 작성하지 않았기 때문에 아무 작업도 수행되지 않습니다. 다음 두 섹션에서 이러한 작업을 다룰 것입니다.

페이지에 사용자 및 역할이 표시됩니다.

그림 2: 페이지에 사용자 및 역할이 표시됩니다(전체 크기 이미지를 보려면 클릭).

선택한 사용자가 속한 역할 확인

페이지가 처음 로드되거나 방문자가 드롭다운 목록에서 새 사용자를 선택할 때마다 선택한 사용자가 해당 역할에 속하는 경우에만 지정된 역할 확인란이 선택되도록 의 확인란을 업데이트 UsersRoleList해야 합니다. 이렇게 하려면 다음 코드를 사용하여 라는 메서드를 만듭니다 CheckRolesForSelectedUser .

private void CheckRolesForSelectedUser() 
{ 
     // Determine what roles the selected user belongs to 
     string selectedUserName = UserList.SelectedValue; 
     string[] selectedUsersRoles = Roles.GetRolesForUser(selectedUserName); 

     // Loop through the Repeater's Items and check or uncheck the checkbox as needed 

     foreach (RepeaterItem ri in UsersRoleList.Items) 
     { 
          // Programmatically reference the CheckBox 
          CheckBox RoleCheckBox = ri.FindControl("RoleCheckBox") as CheckBox; 
          // See if RoleCheckBox.Text is in selectedUsersRoles 
          if (selectedUsersRoles.Contains<string>(RoleCheckBox.Text)) 
               RoleCheckBox.Checked = true; 
          else 
               RoleCheckBox.Checked = false; 
     } 
}

위의 코드는 선택한 사용자가 누구인지 확인하는 것으로 시작합니다. 그런 다음 Roles 클래스의 GetRolesForUser(userName) 메서드 를 사용하여 지정된 사용자의 역할 집합을 문자열 배열로 반환합니다. 다음으로, Repeater의 항목이 열거되고 각 항목의 RoleCheckBox CheckBox가 프로그래밍 방식으로 참조됩니다. CheckBox는 해당하는 역할이 문자열 배열 내에 포함된 경우에만 확인란을 selectedUsersRoles 선택합니다.

참고

selectedUserRoles.Contains<string>(...) ASP.NET 버전 2.0을 사용하는 경우 구문이 컴파일되지 않습니다. 메서드는 Contains<string>LINQ 라이브러리의 일부이며 ASP.NET 3.5의 새로운 기능입니다. ASP.NET 버전 2.0을 계속 사용하는 경우 메서드를 Array.IndexOf<string> 대신 사용합니다.

CheckRolesForSelectedUser 페이지가 처음 로드될 때와 DropDownList의 선택한 인덱스가 변경될 때마다 UserList 메서드를 호출해야 합니다. 따라서 이벤트 처리기에서 이 메서드를 Page_Load 호출합니다(및 BindRolesToList에 대한 호출 BindUsersToUserList 후). 또한 DropDownList의 SelectedIndexChanged 이벤트에 대한 이벤트 처리기를 만들고 여기에서 이 메서드를 호출합니다.

protected void Page_Load(object sender, EventArgs e) 
{ 
     if (!Page.IsPostBack) 
     { 

          // Bind the users and roles 
          BindUsersToUserList(); 
          BindRolesToList(); 
          // Check the selected user's roles 
          CheckRolesForSelectedUser(); 
     } 
} 

... 

protected void UserList_SelectedIndexChanged(object sender, EventArgs e) 
{ 
     CheckRolesForSelectedUser(); 
}

이 코드를 사용하면 브라우저를 통해 페이지를 테스트할 수 있습니다. 그러나 UsersAndRoles.aspx 페이지에는 현재 역할에 사용자를 할당할 수 있는 기능이 없으므로 사용자에게 역할이 없습니다. 잠시 후에 사용자를 역할에 할당하기 위한 인터페이스를 만들겠습니다. 따라서 이 코드가 작동하는 내 단어를 가져와서 나중에 작동하는지 확인하거나, 지금 이 기능을 테스트하기 위해 테이블에 레코드 aspnet_UsersInRoles 를 삽입하여 역할에 사용자를 수동으로 추가할 수 있습니다.

역할에서 사용자 할당 및 제거

방문자가 반복기에서 UsersRoleList CheckBox를 확인하거나 선택 취소하면 해당 역할에서 선택한 사용자를 추가하거나 제거해야 합니다. CheckBox의 AutoPostBack 속성은 현재 True로 설정되어 있으므로 반복기에서 CheckBox를 선택하거나 선택 취소할 때마다 포스트백이 발생합니다. 즉, CheckBox CheckChanged 의 이벤트에 대한 이벤트 처리기를 만들어야 합니다. CheckBox는 Repeater 컨트롤에 있으므로 이벤트 처리기 배관을 수동으로 추가해야 합니다. 먼저 다음과 같이 코드 숨김 클래스에 이벤트 처리기를 메서드로 protected 추가합니다.

protected void RoleCheckBox_CheckChanged(object sender, EventArgs e) 
{ 

}

잠시 후에 이 이벤트 처리기에 대한 코드를 작성하기 위해 돌아갑니다. 하지만 먼저 이벤트 처리 배관을 완료해 보겠습니다. 반복기 내의 ItemTemplateCheckBox에서 를 추가합니다 OnCheckedChanged="RoleCheckBox_CheckChanged". 이 구문은 RoleCheckBox_CheckChanged 이벤트 처리기를 RoleCheckBoxCheckedChanged 이벤트에 연결합니다.

<asp:CheckBox runat="server" ID="RoleCheckBox" 
     AutoPostBack="true" 
     Text='<%# Container.DataItem %>' 
     OnCheckedChanged="RoleCheckBox_CheckChanged" />

마지막 작업은 이벤트 처리기를 완료하는 RoleCheckBox_CheckChanged 것입니다. 이 CheckBox instance 및 속성을 통해 확인되거나 선택 취소된 역할을 알려주므로 이벤트를 발생시킨 CheckBox 컨트롤을 TextChecked 참조하는 것으로 시작해야 합니다. 선택한 사용자의 UserName과 함께 이 정보를 사용하여 클래스 AddUserToRole 또는 메서드를 통해 Roles 역할에서 사용자를 추가하거나 RemoveUserFromRole 제거합니다.

protected void RoleCheckBox_CheckChanged(object sender, EventArgs e) 
{ 
     // Reference the CheckBox that raised this event 
     CheckBox RoleCheckBox = sender as CheckBox; 

     // Get the currently selected user and role 
     string selectedUserName = UserList.SelectedValue; 

     string roleName = RoleCheckBox.Text; 

     // Determine if we need to add or remove the user from this role 
     if (RoleCheckBox.Checked) 
     { 
          // Add the user to the role 
          Roles.AddUserToRole(selectedUserName, roleName); 
          // Display a status message 
          ActionStatus.Text = string.Format("User {0} was added to role {1}.", selectedUserName, roleName); 
     } 
     else 
     { 
          // Remove the user from the role 
          Roles.RemoveUserFromRole(selectedUserName, roleName); 
          // Display a status message 
          ActionStatus.Text = string.Format("User {0} was removed from role {1}.", selectedUserName, roleName); 

     } 
}

위의 코드는 입력 매개 변수를 통해 sender 사용할 수 있는 이벤트를 발생시킨 CheckBox를 프로그래밍 방식으로 참조하여 시작합니다. CheckBox를 선택하면 선택한 사용자가 지정된 역할에 추가되고, 그렇지 않으면 역할에서 제거됩니다. 두 경우 모두 레이블은 ActionStatus 방금 수행한 작업을 요약하는 메시지를 표시합니다.

잠시 시간을 내어 브라우저를 통해 이 페이지를 테스트합니다. 사용자 Tito를 선택한 다음, 관리자 및 감독자 역할 모두에 Tito를 추가합니다.

Tito가 관리자 및 감독자 역할에 추가되었습니다.

그림 3: Tito가 관리자 및 감독자 역할에 추가되었습니다(전체 크기 이미지를 보려면 클릭).

다음으로, 드롭다운 목록에서 사용자 Bruce를 선택합니다. 포스트백이 있고 Repeater의 CheckBoxes는 를 CheckRolesForSelectedUser통해 업데이트됩니다. Bruce는 아직 역할에 속하지 않으므로 두 확인란의 선택을 취소합니다. 다음으로, 감독자 역할에 Bruce를 추가합니다.

브루스가 감독자 역할에 추가되었습니다.

그림 4: Bruce가 감독자 역할에 추가되었습니다(전체 크기 이미지를 보려면 클릭).

메서드의 CheckRolesForSelectedUser 기능을 추가로 확인하려면 Tito 또는 Bruce 이외의 사용자를 선택합니다. 확인란이 역할에 속하지 않음을 나타내는 확인란이 자동으로 선택 취소되는 방식을 확인합니다. Tito로 돌아갑니다. 관리자 및 감독자 확인란을 모두 선택해야 합니다.

2단계: "역할별" 사용자 인터페이스 빌드

이 시점에서 "사용자별" 인터페이스를 완료하고 "역할별" 인터페이스를 다룰 준비가 된 것입니다. "역할별" 인터페이스는 사용자에게 드롭다운 목록에서 역할을 선택하라는 메시지를 표시한 다음 GridView에서 해당 역할에 속한 사용자 집합을 표시합니다.

페이지에 다른 DropDownList 컨트롤을 추가합니다 UsersAndRoles.aspx . 이 컨트롤을 Repeater 컨트롤 아래에 놓고 이름을 로 지정 RoleList하고 속성을 AutoPostBack True로 설정합니다. 그 아래에 GridView를 추가하고 이름을 로 지정합니다 RolesUserList. 이 GridView는 선택한 역할에 속한 사용자를 나열합니다. GridView의 AutoGenerateColumns 속성을 False로 설정하고, Grid 컬렉션 Columns 에 TemplateField를 추가하고, 해당 HeaderText 속성을 "Users"로 설정합니다. 라는 LabelUserNameLabel의 속성에 데이터 바인딩 식 TextContainer.DataItem 의 값을 표시할 수 있도록 TemplateField ItemTemplate 를 정의합니다.

GridView를 추가하고 구성한 후 "역할별" 인터페이스의 선언적 태그는 다음과 유사하게 표시됩니다.

<h3>Manage Users By Role</h3> 
<p> 
     <b>Select a Role:</b> 

     <asp:DropDownList ID="RoleList" runat="server" AutoPostBack="true"></asp:DropDownList> 
</p> 
<p>      <asp:GridView ID="RolesUserList" runat="server" AutoGenerateColumns="false" 

          EmptyDataText="No users belong to this role."> 
          <Columns> 
               <asp:TemplateField HeaderText="Users"> 
                    <ItemTemplate> 
                         <asp:Label runat="server" id="UserNameLabel" 
                              Text='<%# Container.DataItem %>'></asp:Label> 

                    </ItemTemplate> 
               </asp:TemplateField> 
          </Columns> 
     </asp:GridView> </p>

DropDownList를 RoleList 시스템의 역할 집합으로 채워야 합니다. 이렇게 하려면 메서드가 BindRolesToList 반환한 문자열 배열을 DropDownList(및 반복기)에 Roles.GetAllRoles 바인딩하도록 RolesList 메서드를 UsersRoleList 업데이트합니다.

private void BindRolesToList() 
{ 
     // Get all of the roles 

     string[] roles = Roles.GetAllRoles(); 
     UsersRoleList.DataSource = roles; 
     UsersRoleList.DataBind(); 

     RoleList.DataSource = roles; 
     RoleList.DataBind(); 
}

메서드의 BindRolesToList 마지막 두 줄이 추가되어 역할 집합을 DropDownList 컨트롤에 RoleList 바인딩합니다. 그림 5는 시스템 역할로 채워진 드롭다운 목록인 브라우저를 통해 볼 때의 최종 결과를 보여 줍니다.

역할은 RoleList DropDownList에 표시됩니다.

그림 5: 역할이 DropDownList에 RoleList 표시됩니다(전체 크기 이미지를 보려면 클릭).

선택한 역할에 속한 사용자 표시

페이지가 처음 로드되거나 DropDownList에서 새 역할이 선택되면 GridView에서 RoleList 해당 역할에 속한 사용자 목록을 표시해야 합니다. 다음 코드를 사용하여 라는 DisplayUsersBelongingToRole 메서드를 만듭니다.

private void DisplayUsersBelongingToRole() 
{ 
     // Get the selected role 
     string selectedRoleName = RoleList.SelectedValue; 

     // Get the list of usernames that belong to the role 
     string[] usersBelongingToRole = Roles.GetUsersInRole(selectedRoleName); 

     // Bind the list of users to the GridView 
     RolesUserList.DataSource = usersBelongingToRole; 
     RolesUserList.DataBind(); 
}

이 메서드는 DropDownList에서 선택한 역할을 가져오는 것으로 RoleList 시작합니다. 그런 다음 메서드를 Roles.GetUsersInRole(roleName) 사용하여 해당 역할에 속한 사용자의 UserNames 문자열 배열을 검색합니다. 그런 다음 이 배열은 GridView에 RolesUserList 바인딩됩니다.

이 메서드는 페이지가 처음 로드되는 경우와 DropDownList에서 RoleList 선택한 역할이 변경되는 경우의 두 가지 상황에서 호출해야 합니다. 따라서 에 대한 Page_Load 호출 후에 이 메서드가 호출되도록 이벤트 처리기를 업데이트합니다 CheckRolesForSelectedUser. 다음으로, 의 SelectedIndexChanged 이벤트에 대한 RoleList이벤트 처리기를 만들고 여기에서도 이 메서드를 호출합니다.

protected void Page_Load(object sender, EventArgs e) 
{ 
     if (!Page.IsPostBack) 
     { 
          // Bind the users and roles 
          BindUsersToUserList(); 
          BindRolesToList(); 

          // Check the selected user's roles 
          CheckRolesForSelectedUser(); 

          // Display those users belonging to the currently selected role 
          DisplayUsersBelongingToRole(); 
     } 
} 

... 

protected void RoleList_SelectedIndexChanged(object sender, EventArgs e) 
{ 
     DisplayUsersBelongingToRole(); 
}

이 코드를 적용하면 GridView에서 RolesUserList 선택한 역할에 속한 사용자를 표시해야 합니다. 그림 6에서 보여 주듯이 감독자 역할은 브루스와 티토의 두 멤버로 구성됩니다.

GridView는 선택한 역할에 속한 사용자를 나열합니다.

그림 6: GridView는 선택한 역할에 속한 사용자를 나열합니다(전체 크기 이미지를 보려면 클릭).

선택한 역할에서 사용자 제거

"제거" 단추의 열을 포함하도록 GridView를 보강 RolesUserList 해 보겠습니다. 특정 사용자의 "제거" 단추를 클릭하면 해당 역할에서 제거됩니다.

먼저 GridView에 삭제 단추 필드를 추가합니다. 이 필드를 가장 많이 제출된 왼쪽으로 표시하고 해당 DeleteText 속성을 "Delete"(기본값)에서 "제거"로 변경합니다.

필드 창에서

그림 7: GridView에 "제거" 단추 추가(전체 크기 이미지를 보려면 클릭)

"제거" 단추를 클릭하면 포스트백이 계속되고 GridView의 RowDeleting 이벤트가 발생합니다. 이 이벤트에 대한 이벤트 처리기를 만들고 선택한 역할에서 사용자를 제거하는 코드를 작성해야 합니다. 이벤트 처리기를 만든 다음, 다음 코드를 추가합니다.

protected void RolesUserList_RowDeleting(object sender, GridViewDeleteEventArgs e) 
{ 
     // Get the selected role 
     string selectedRoleName = RoleList.SelectedValue; 

     // Reference the UserNameLabel 
     Label UserNameLabel = RolesUserList.Rows[e.RowIndex].FindControl("UserNameLabel") as Label; 

     // Remove the user from the role 
     Roles.RemoveUserFromRole(UserNameLabel.Text, selectedRoleName); 

     // Refresh the GridView 
     DisplayUsersBelongingToRole(); 

     // Display a status message 
     ActionStatus.Text = string.Format("User {0} was removed from role {1}.", UserNameLabel.Text, selectedRoleName); 
}

코드는 선택한 역할 이름을 확인하여 시작합니다. 그런 다음 제거할 사용자의 UserName을 확인하기 위해 "제거" 단추를 클릭한 행의 컨트롤을 프로그래밍 방식으로 참조 UserNameLabel 합니다. 그런 다음 메서드에 대한 호출을 통해 사용자가 역할에서 제거됩니다 Roles.RemoveUserFromRole . RolesUserList 그러면 GridView가 새로 고쳐지고 레이블 컨트롤을 ActionStatus 통해 메시지가 표시됩니다.

참고

"제거" 단추는 역할에서 사용자를 제거하기 전에 사용자로부터 어떤 종류의 확인도 요구하지 않습니다. 일부 수준의 사용자 확인을 추가하도록 초대합니다. 작업을 확인하는 가장 쉬운 방법 중 하나는 클라이언트 쪽 확인 대화 상자를 사용하는 것입니다. 이 기술에 대한 자세한 내용은 삭제 시 Client-Side 확인 추가를 참조하세요.

그림 8은 사용자 Tito가 감독자 그룹에서 제거된 후의 페이지를 보여줍니다.

아아, 티토는 더 이상 감독자가 아닙니다.

그림 8: Alas, Tito가 더 이상 감독자가 아닙니다(전체 크기 이미지를 보려면 클릭)

선택한 역할에 새 사용자 추가

선택한 역할에서 사용자를 제거하는 것 외에도 이 페이지의 방문자는 선택한 역할에 사용자를 추가할 수 있어야 합니다. 선택한 역할에 사용자를 추가하는 데 가장 적합한 인터페이스는 예상되는 사용자 계정 수에 따라 달라집니다. 웹 사이트에 수십 개 이하의 사용자 계정이 있는 경우 여기에서 DropDownList를 사용할 수 있습니다. 수천 개의 사용자 계정이 있을 수 있는 경우 방문자가 계정을 페이지로 이동하거나, 특정 계정을 검색하거나, 다른 방식으로 사용자 계정을 필터링할 수 있도록 허용하는 사용자 인터페이스를 포함해야 합니다.

이 페이지에서는 시스템의 사용자 계정 수에 관계없이 작동하는 매우 간단한 인터페이스를 사용해 보겠습니다. 즉, TextBox를 사용하여 방문자에게 선택한 역할에 추가하려는 사용자의 사용자 이름을 입력하라는 메시지를 표시합니다. 해당 이름을 가진 사용자가 없거나 사용자가 이미 역할의 멤버인 경우 레이블에 ActionStatus 메시지를 표시합니다. 그러나 사용자가 존재하고 역할의 멤버가 아닌 경우 역할에 추가하고 그리드를 새로 고칩니다.

GridView 아래에 TextBox 및 단추를 추가합니다. TextBox의 ID 를 로 UserNameToAddToRole 설정하고 Button의 ID 및 속성을 AddUserToRoleButton 각각 및 Text "역할에 사용자 추가"로 설정합니다.

<p> 
     <b>UserName:</b> 
     <asp:TextBox ID="UserNameToAddToRole" runat="server"></asp:TextBox> 
     <br /> 
     <asp:Button ID="AddUserToRoleButton" runat="server" Text="Add User to Role" /> 

</p>

다음으로 에 대한 AddUserToRoleButton 이벤트 처리기를 만들고 Click 다음 코드를 추가합니다.

protected void AddUserToRoleButton_Click(object sender, EventArgs e) 
{ 
     // Get the selected role and username 

     string selectedRoleName = RoleList.SelectedValue; 
     string userNameToAddToRole = UserNameToAddToRole.Text; 

     // Make sure that a value was entered 
     if (userNameToAddToRole.Trim().Length == 0) 
     { 
          ActionStatus.Text = "You must enter a username in the textbox."; 
          return; 
     } 

     // Make sure that the user exists in the system 
     MembershipUser userInfo = Membership.GetUser(userNameToAddToRole); 
     if (userInfo == null) 
     { 
          ActionStatus.Text = string.Format("The user {0} does not exist in the system.", userNameToAddToRole); 

          return; 
     } 

     // Make sure that the user doesn't already belong to this role 
     if (Roles.IsUserInRole(userNameToAddToRole, selectedRoleName)) 
     { 
          ActionStatus.Text = string.Format("User {0} already is a member of role {1}.", userNameToAddToRole, selectedRoleName); 
          return; 
     } 

     // If we reach here, we need to add the user to the role 
     Roles.AddUserToRole(userNameToAddToRole, selectedRoleName); 

     // Clear out the TextBox 
     UserNameToAddToRole.Text = string.Empty; 

     // Refresh the GridView 
     DisplayUsersBelongingToRole(); 

     // Display a status message 

     ActionStatus.Text = string.Format("User {0} was added to role {1}.", userNameToAddToRole, selectedRoleName); }

이벤트 처리기의 코드 대부분은 Click 다양한 유효성 검사를 수행합니다. 방문자가 TextBox에 UserNameToAddToRole 사용자 이름을 제공하고, 사용자가 시스템에 있고, 선택한 역할에 아직 속하지 않도록 합니다. 이러한 검사가 실패하면 적절한 메시지가 에 표시 ActionStatus 되고 이벤트 처리기가 종료됩니다. 모든 검사가 통과하면 메서드를 통해 Roles.AddUserToRole 사용자가 역할에 추가됩니다. 그런 다음 TextBox의 Text 속성이 지워지고 GridView가 새로 고쳐지고 ActionStatus 레이블에 지정된 사용자가 선택한 역할에 성공적으로 추가되었음을 나타내는 메시지가 표시됩니다.

참고

지정된 사용자가 선택한 역할에 아직 속하지 않도록 하려면 userNameroleName의 멤버인지 여부를 나타내는 부울 값을 반환하는 메서드를 사용합니다Roles.IsUserInRole(userName, roleName). 역할 기반 권한 부여를 살펴볼 때 다음 자습서에서 이 메서드를 다시 사용합니다.

브라우저를 통해 페이지를 방문하여 DropDownList에서 RoleList 감독자 역할을 선택합니다. 잘못된 사용자 이름을 입력해 보세요. 시스템에 사용자가 존재하지 않는다는 메시지가 표시됩니다.

역할에 존재하지 않는 사용자를 추가할 수 없습니다.

그림 9: 존재하지 않는 사용자를 역할에 추가할 수 없습니다(전체 크기 이미지를 보려면 클릭).

이제 유효한 사용자를 추가해 봅니다. 계속 진행하여 감독자 역할에 Tito를 다시 추가합니다.

티토는 다시 한 번 감독자입니다!

그림 10: 티토는 다시 한 번 감독자입니다! (전체 크기 이미지를 보려면 클릭)

3단계: "사용자별" 및 "역할별" 인터페이스 교차 업데이트

이 페이지에서는 UsersAndRoles.aspx 사용자 및 역할을 관리하기 위한 두 가지 고유한 인터페이스를 제공합니다. 현재 이러한 두 인터페이스는 서로 독립적으로 작동하므로 한 인터페이스에서 변경한 내용이 다른 인터페이스에 즉시 반영되지 않을 수 있습니다. 예를 들어 페이지 방문자가 Bruce와 Tito를 멤버로 나열하는 DropDownList에서 RoleList 감독자 역할을 선택한다고 상상해 보십시오. 다음으로, 방문자는 DropDownList에서 UserList Tito를 선택합니다. 그러면 반복기에서 관리자 및 감독자 확인란을 UsersRoleList 확인합니다. 방문자가 반복기에서 감독자 역할을 선택 취소하면 Tito가 감독자 역할에서 제거되지만 이 수정 내용은 "역할별" 인터페이스에 반영되지 않습니다. GridView는 여전히 Tito가 감독자 역할의 구성원으로 표시됩니다.

이 문제를 해결하려면 리피터에서 역할을 선택하거나 선택 취소할 때마다 GridView를 UsersRoleList 새로 고쳐야 합니다. 마찬가지로 사용자가 제거되거나 "역할별" 인터페이스에서 역할에 추가될 때마다 Repeater를 새로 고쳐야 합니다.

"사용자별" 인터페이스의 반복기는 메서드를 호출하여 새로 고쳐집니다 CheckRolesForSelectedUser . "역할별" 인터페이스는 GridView의 RowDeleting 이벤트 처리기 및 AddUserToRoleButton Button의 Click 이벤트 처리기에서 RolesUserList 수정할 수 있습니다. 따라서 이러한 각 메서드에서 메서드를 CheckRolesForSelectedUser 호출해야 합니다.

protected void RolesUserList_RowDeleting(object sender, GridViewDeleteEventArgs e) 
{ 
     ... Code removed for brevity ... 

     // Refresh the "by user" interface 
     CheckRolesForSelectedUser(); 
} 

protected void AddUserToRoleButton_Click(object sender, EventArgs e) 
{ 
     ... Code removed for brevity ... 


     // Refresh the "by user" interface 
     CheckRolesForSelectedUser(); 
}

마찬가지로 "역할별" 인터페이스의 GridView는 메서드를 DisplayUsersBelongingToRole 호출하여 새로 고쳐지고 이벤트 처리기를 통해 "사용자별" 인터페이스가 RoleCheckBox_CheckChanged 수정됩니다. 따라서 이 이벤트 처리기에서 메서드를 DisplayUsersBelongingToRole 호출해야 합니다.

protected void RoleCheckBox_CheckChanged(object sender, EventArgs e) 
{ 
     ... Code removed for brevity... 

     // Refresh the "by role" interface 
     DisplayUsersBelongingToRole(); 
}

이러한 사소한 코드 변경으로 "사용자별" 및 "역할별" 인터페이스가 이제 올바르게 교차 업데이트됩니다. 이를 확인하려면 브라우저를 통해 페이지를 방문하여 및 RoleList DropDownLists에서 UserList Tito 및 감독자를 각각 선택합니다. "사용자별" 인터페이스의 반복기에서 Tito에 대한 감독자 역할을 선택 취소하면 Tito가 "역할별" 인터페이스의 GridView에서 자동으로 제거됩니다. "역할별" 인터페이스에서 감독자 역할에 Tito를 다시 추가하면 "사용자별" 인터페이스의 감독자 확인란이 자동으로 다시 검사됩니다.

4단계: "역할 지정" 단계를 포함하도록 CreateUserWizard 사용자 지정

사용자 계정 만들기 자습서에서는 CreateUserWizard 웹 컨트롤을 사용하여 새 사용자 계정을 만들기 위한 인터페이스를 제공하는 방법을 알아보았습니다. CreateUserWizard 컨트롤은 다음 두 가지 방법 중 하나로 사용할 수 있습니다.

  • 방문자가 사이트에 자신의 사용자 계정을 만들 수 있는 수단으로, 및
  • 관리자가 새 계정을 만들 수 있는 수단으로

첫 번째 사용 사례에서 방문자가 사이트에 와서 CreateUserWizard를 채우고 사이트에 등록하기 위해 정보를 입력합니다. 두 번째 경우 관리자는 다른 사람에 대한 새 계정을 만듭니다.

관리자가 다른 사람에 대해 계정을 만드는 경우 관리자가 새 사용자 계정이 속한 역할을 지정할 수 있도록 하는 것이 도움이 될 수 있습니다. 추가 사용자 정보저장 자습서에서는 를 추가하여 WizardStepsCreateUserWizard를 사용자 지정하는 방법을 알아보았습니다. 새 사용자의 역할을 지정하기 위해 CreateUserWizard에 추가 단계를 추가하는 방법을 살펴보겠습니다.

CreateUserWizardWithRoles.aspx 페이지를 열고 라는 RegisterUserWithRolesCreateUserWizard 컨트롤을 추가합니다. 컨트롤의 ContinueDestinationPageUrl 속성을 "~/Default.aspx"로 설정합니다. 여기서는 관리자가 이 CreateUserWizard 컨트롤을 사용하여 새 사용자 계정을 만들므로 컨트롤의 LoginCreatedUser 속성을 False로 설정합니다. 이 LoginCreatedUser 속성은 방문자가 방금 만든 사용자로 자동으로 로그온되는지 여부를 지정하며 기본값은 True입니다. 관리자가 새 계정을 만들 때 직접 로그인한 상태로 유지하려고 하므로 False로 설정합니다.

다음으로 ,"추가/제거 WizardSteps..."를 선택합니다. 옵션을 선택하면 CreateUserWizard의 스마트 태그에서 새 를 WizardStep추가하고 를 IDSpecifyRolesStep설정합니다. SpecifyRolesStep WizardStep"새 계정에 등록" 단계가 끝난 후 "완료" 단계 앞에 오도록 을 이동합니다. WizardStepTitle 속성을 "역할 지정"으로, 해당 StepType 속성을 Step로 설정하고, 해당 AllowReturn 속성을 False로 설정합니다.

마법사 단계 컬렉션 편집기 창에서 선택한 역할 지정 속성을 보여 주는 스크린샷

그림 11: CreateUserWizard에 "역할 지정" WizardStep 을 추가합니다(전체 크기 이미지를 보려면 클릭).

이 변경 후 CreateUserWizard의 선언적 태그는 다음과 같이 표시됩니다.

<asp:CreateUserWizard ID="RegisterUserWithRoles" runat="server" 
     ContinueDestinationPageUrl="~/Default.aspx" LoginCreatedUser="False"> 

     <WizardSteps> 
          <asp:CreateUserWizardStep ID="CreateUserWizardStep1" runat="server"> 
          </asp:CreateUserWizardStep> 
          <asp:WizardStep ID="SpecifyRolesStep" runat="server" StepType="Step" 

               Title="Specify Roles" AllowReturn="False"> 
          </asp:WizardStep> 
          <asp:CompleteWizardStep ID="CompleteWizardStep1" runat="server"> 
          </asp:CompleteWizardStep> 
     </WizardSteps> 

</asp:CreateUserWizard>

"역할 지정" WizardStep에서 이라는 RoleListCheckBoxList를 추가합니다. 이 CheckBoxList에는 사용 가능한 역할이 나열되어 페이지를 방문하는 사용자가 새로 만든 사용자가 속한 역할을 검사 수 있습니다.

두 가지 코딩 작업이 남아 있습니다. 먼저 CheckBoxList를 시스템의 역할로 채워 RoleList 야 합니다. 둘째, 사용자가 "역할 지정" 단계에서 "완료" 단계로 이동할 때 생성된 사용자를 선택한 역할에 추가해야 합니다. 이벤트 처리기에서 Page_Load 첫 번째 작업을 수행할 수 있습니다. 다음 코드는 첫 번째 페이지 방문에서 CheckBox를 프로그래밍 방식으로 참조 RoleList 하고 시스템의 역할을 바인딩합니다.

protected void Page_Load(object sender, EventArgs e) 
{ 
     if (!Page.IsPostBack) 
     { 
          // Reference the SpecifyRolesStep WizardStep 
          WizardStep SpecifyRolesStep = RegisterUserWithRoles.FindControl("SpecifyRolesStep") as WizardStep; 

          // Reference the RoleList CheckBoxList 
          CheckBoxList RoleList = SpecifyRolesStep.FindControl("RoleList") as CheckBoxList; 

          // Bind the set of roles to RoleList 
          RoleList.DataSource = Roles.GetAllRoles(); 
          RoleList.DataBind(); 
     } 
}

위의 코드는 친숙해 보일 것입니다. 추가 사용자 정보저장 자습서에서는 두 개의 FindControl 문을 사용하여 사용자 지정 WizardStep내에서 웹 컨트롤을 참조했습니다. 또한 역할을 CheckBoxList에 바인딩하는 코드는 이 자습서의 앞부분에서 가져온 것입니다.

두 번째 프로그래밍 작업을 수행하려면 "역할 지정" 단계가 완료된 시기를 알아야 합니다. CreateUserWizard에는 ActiveStepChanged 방문자가 한 단계에서 다른 단계로 이동할 때마다 발생하는 이벤트가 있습니다. 여기서는 사용자가 "완료" 단계에 도달했는지 확인할 수 있습니다. 그렇다면 선택한 역할에 사용자를 추가해야 합니다.

이벤트에 대한 ActiveStepChanged 이벤트 처리기를 만들고 다음 코드를 추가합니다.

protected void RegisterUserWithRoles_ActiveStepChanged(object sender, EventArgs e) 
{ 
     // Have we JUST reached the Complete step? 
     if (RegisterUserWithRoles.ActiveStep.Title == "Complete") 
     { 
          // Reference the SpecifyRolesStep WizardStep 
          WizardStep SpecifyRolesStep = RegisterUserWithRoles.FindControl("SpecifyRolesStep") as WizardStep; 

          // Reference the RoleList CheckBoxList 
          CheckBoxList RoleList = SpecifyRolesStep.FindControl("RoleList") as CheckBoxList; 

          // Add the checked roles to the just-added user 
          foreach (ListItem li in RoleList.Items) 

          { 
               if (li.Selected) 
                    Roles.AddUserToRole(RegisterUserWithRoles.UserName, li.Text); 
          } 
     } 
}

사용자가 "완료됨" 단계에 도달한 경우 이벤트 처리기는 CheckBoxList의 RoleList 항목을 열거하고 방금 만든 사용자가 선택한 역할에 할당됩니다.

브라우저를 통해 이 페이지를 방문하세요. CreateUserWizard의 첫 번째 단계는 새 사용자의 사용자 이름, 암호, 전자 메일 및 기타 주요 정보를 묻는 표준 "새 계정 등록" 단계입니다. 정보를 입력하여 Wanda라는 새 사용자를 만듭니다.

Wanda라는 새 사용자 만들기

그림 12: Wanda라는 새 사용자 만들기(전체 크기 이미지를 보려면 클릭)

"사용자 만들기" 단추를 클릭합니다. CreateUserWizard는 내부적으로 메서드를 Membership.CreateUser 호출하여 새 사용자 계정을 만든 다음 다음 단계인 "역할 지정"으로 진행합니다. 여기에 시스템 역할이 나열됩니다. 감독자 확인란을 선택하고 다음을 클릭합니다.

Wanda를 감독자 역할의 구성원으로 만들기

그림 13: Wanda를 감독자 역할의 구성원으로 만들기(전체 크기 이미지를 보려면 클릭)

다음을 클릭하면 포스트백이 발생하고 가 ActiveStep "완료" 단계로 업데이트됩니다. 이벤트 처리기 ActiveStepChanged 에서 최근에 만든 사용자 계정이 감독자 역할에 할당됩니다. 이를 확인하려면 페이지로 돌아가 UsersAndRoles.aspx 서 DropDownList에서 RoleList 감독자를 선택합니다. 그림 14와 같이 감독자는 이제 브루스, 티토, 완다의 세 명의 사용자로 구성됩니다.

브루스, 티토, 완다는 모든 감독자입니다.

그림 14: Bruce, Tito 및 Wanda는 모든 감독자입니다(전체 크기 이미지를 보려면 클릭).

요약

역할 프레임워크는 특정 사용자의 역할에 대한 정보를 검색하는 메서드와 지정된 역할에 속한 사용자를 결정하는 메서드를 제공합니다. 또한 하나 이상의 사용자를 하나 이상의 역할에 추가하고 제거하는 방법에는 여러 가지가 있습니다. 이 자습서에서는 두 가지 방법 AddUserToRole 인 및 RemoveUserFromRole에만 집중했습니다. 단일 역할에 여러 사용자를 추가하고 단일 사용자에게 여러 역할을 할당하도록 설계된 추가 변형이 있습니다.

또한 이 자습서에서는 새로 만든 사용자의 역할을 지정하는 를 포함 WizardStep 하도록 CreateUserWizard 컨트롤을 확장하는 방법을 살펴보았습니다. 이러한 단계는 관리자가 새 사용자를 위한 사용자 계정을 만드는 프로세스를 간소화하는 데 도움이 될 수 있습니다.

이 시점에서 역할을 만들고 삭제하는 방법과 역할에서 사용자를 추가 및 제거하는 방법을 알아보았습니다. 그러나 역할 기반 권한 부여를 적용하는 것은 아직 살펴보지 않았습니다. 다음 자습서에서는 역할별로 URL 권한 부여 규칙을 정의하는 방법과 현재 로그인한 사용자의 역할에 따라 페이지 수준 기능을 제한하는 방법을 살펴봅니다.

행복한 프로그래밍!

추가 정보

이 자습서에서 설명하는 topics 대한 자세한 내용은 다음 리소스를 참조하세요.

저자 정보

여러 ASP/ASP.NET 책의 저자이자 4GuysFromRolla.com 설립자인 Scott Mitchell은 1998년부터 Microsoft 웹 기술을 사용하고 있습니다. Scott은 독립 컨설턴트, 트레이너 및 작가로 일합니다. 그의 최신 책은 샘스 자신을 가르친다 ASP.NET 2.0 24 시간. Scott은 에서 mitchell@4guysfromrolla.com 또는 에서 자신의 블로그 http://ScottOnWriting.NET를 통해 연락할 수 있습니다.

특별한 감사...

이 자습서 시리즈는 많은 유용한 검토자가 검토했습니다. 이 자습서의 수석 검토자는 테레사 머피였습니다. 예정된 MSDN 문서를 검토하시겠습니까? 그렇다면 에서 줄을 놓습니다. mitchell@4GuysFromRolla.com