Share via


IIS 7.0 통합 파이프라인을 활용하는 방법

작성자 : Mike Volodarsky

IIS 6.0 및 이전 버전에서는 ASP.NET 플랫폼을 통해 .NET 애플리케이션 구성 요소를 개발할 수 있습니다. ASP.NET ISAPI 확장을 통해 IIS와 통합되고 자체 애플리케이션 및 요청 처리 모델을 노출했습니다. 이렇게 하면 네이티브 ISAPI 필터 및 확장 구성 요소용과 관리되는 애플리케이션 구성 요소에 대한 두 개의 개별 서버 파이프라인이 효과적으로 노출되었습니다. ASP.NET 구성 요소는 ASP.NET ISAPI 확장 버블 내에서 전적으로 실행되며 IIS 스크립트 맵 구성의 ASP.NET 매핑된 요청에 대해서만 실행됩니다.

IIS 7.0 이상에서는 ASP.NET 런타임을 핵심 웹 서버와 통합하여 모듈이라고 하는 네이티브 구성 요소와 관리되는 구성 요소 모두에 노출되는 통합 요청 처리 파이프라인을 제공합니다. 통합의 많은 이점은 다음과 같습니다.

  • 네이티브 모듈과 관리되는 모듈에서 제공하는 서비스가 처리기에 관계없이 모든 요청에 적용되도록 허용합니다. 예를 들어 관리형 Forms 인증은 ASP 페이지, CGI 및 정적 파일을 비롯한 모든 콘텐츠에 사용할 수 있습니다.
  • 서버 파이프라인에 배치되어 이전에 사용할 수 없었던 기능을 제공하기 위해 ASP.NET 구성 요소의 권한을 부여합니다. 예를 들어 요청 다시 쓰기 기능을 제공하는 관리되는 모듈은 인증을 포함하여 서버 처리 전에 요청을 다시 작성할 수 있습니다.
  • 단일 모듈 및 처리기 매핑 구성, 단일 사용자 지정 오류 구성, 단일 URL 권한 부여 구성과 같은 서버 기능을 구현, 구성, 모니터링 및 지원하는 단일 위치입니다.

이 문서에서는 ASP.NET 애플리케이션이 IIS 7.0 이상의 통합 모드를 활용하는 방법을 살펴보고 다음 작업을 보여 줍니다.

  • 애플리케이션별 수준에서 모듈 사용/사용 안 함
  • 관리되는 애플리케이션 모듈을 서버에 추가하고 모든 요청 형식에 적용할 수 있도록 합니다.
  • 관리되는 처리기 추가.

.NET FRAMEWORK 사용하여 IIS 7.0 이상 모듈 및 처리기 개발에서 IIS 7.0 이상 모듈을 빌드하는 방법에 대해 자세히 알아봅니다.

통합 모드를 활용하고 IIS 7.0 이상에서 ASP.NET 통합을 활용하는 IIS 모듈을 개발하는 방법에 대한 자세한 팁은 블로그 http://www.mvolo.com/를 참조하세요. 여기서 HttpRedirection 모듈을 사용하여 애플리케이션에 요청을 리디렉션하고, DirectoryListingModule을 사용하여 IIS 웹 사이트에 대한 멋진 디렉터리 목록을 찾고, IconHandler를 사용하여 ASP.NET 애플리케이션에 예쁜 파일 아이콘 표시를 비롯한 다양한 모듈을 다운로드합니다.

사전 요구 사항

이 문서의 단계를 수행하려면 다음 IIS 7.0 이상 기능을 설치해야 합니다.

ASP.NET

Windows Vista 제어판 통해 ASP.NET 설치합니다. "프로그램 및 기능" - "Windows 기능 켜기 또는 끄기"를 선택합니다. 그런 다음 "인터넷 정보 서비스" - "World Wide Web Services" - "애플리케이션 개발 기능"을 열고 "ASP.NET"을 검사.

Windows Server® 2008 빌드가 있는 경우 "서버 관리자" - "역할"을 열고 "웹 서버(IIS)"를 선택합니다. "역할 서비스 추가"를 클릭합니다. "애플리케이션 개발"에서 "ASP.NET"을 검사.

클래식 ASP

이제 ASP.NET 모듈이 ASP.NET 페이지뿐만 아니라 모든 콘텐츠에서 작동하는 방식을 보여 주려고 하므로 Windows Vista 제어판 통해 클래식 ASP를 설치합니다. "프로그램" - "Windows 기능 켜기 또는 끄기"를 선택합니다. 그런 다음 "인터넷 정보 서비스" - "World Wide Web Services" - "애플리케이션 개발 기능"을 열고 "ASP"를 검사.

Windows Server 2008 빌드가 있는 경우 "서버 관리자" - "역할"을 열고 "웹 서버(IIS)"를 선택합니다. "역할 서비스 추가"를 클릭합니다. "애플리케이션 개발"에서 "ASP"를 검사.

애플리케이션에 양식 인증 추가

이 작업의 일환으로 애플리케이션에 ASP.NET Forms 기반 인증을 사용하도록 설정합니다. 다음 작업에서는 콘텐츠 형식에 관계없이 애플리케이션에 대한 모든 요청에 대해 Forms 인증 모듈을 실행할 수 있습니다.

먼저 일반 ASP.NET 애플리케이션과 마찬가지로 양식 인증을 구성합니다.

샘플 페이지 만들기

이 기능을 설명하기 위해 웹 루트 디렉터리에 default.aspx 페이지를 추가합니다. 메모장을 열고(아래 wwwroot 디렉터리에 대한 액세스 권한이 있는지 확인하려면 관리자 권한으로 를 실행해야 합니다. Programs\Accessories\Notepad 아이콘을 마우스 오른쪽 단추로 클릭하고 "관리자 권한으로 실행"을 클릭합니다.) 다음 파일을 %systemdrive%\inetpub\wwwroot\default.aspx만듭니다. 다음 줄을 붙여넣습니다.

<%=Datetime.Now%> 
<BR> 
Login Name: <asp:LoginName runat="server"/>

모든 default.aspx는 현재 시간과 로그인한 사용자의 이름을 표시합니다. 나중에 이 페이지를 사용하여 양식 인증이 작동하는 모습을 보여 드립니다.

양식 인증 및 Access Control 규칙 구성

이제 양식 인증을 사용하여 default.aspx를 보호합니다. 디렉터리에 web.config 파일을 %systemdrive%\inetpub\wwwroot 만들고 아래에 표시된 구성을 추가합니다.

<configuration> 
  <system.web> 
    <!--membership provider entry goes here--> 
    <authorization> 
      <deny users="?"/> 
      <allow users="*"/> 
    </authorization> 
    <authentication mode="Forms"/> 
  </system.web> 
</configuration>

이 구성은 양식 기반 인증을 사용하도록 ASP.NET 인증 모드를 설정하고 애플리케이션에 대한 액세스를 제어하는 권한 부여 설정을 추가합니다. 이러한 설정은 익명 사용자(?)에 대한 액세스를 거부하고 인증된 사용자(*)만 허용합니다.

멤버 자격 공급자 만들기

1단계: 사용자 자격 증명을 확인할 인증 저장소를 제공해야 합니다. ASP.NET IIS 7.0 이상 간의 긴밀한 통합을 설명하기 위해 자체 XML 기반 멤버 자격 공급자를 사용합니다(SQL Server 설치된 경우 기본 SQL Server 멤버 자격 공급자를 사용할 수도 있음).

web.config 파일의 초기 <configuration>/<system.web> 구성 요소 바로 다음에 다음 항목을 추가합니다.

<membership defaultProvider="AspNetReadOnlyXmlMembershipProvider"> 
  <providers> 
    <add name="AspNetReadOnlyXmlMembershipProvider" type="AspNetReadOnlyXmlMembershipProvider" description="Read-only XML membership provider" xmlFileName="~/App_Data/MembershipUsers.xml"/> 
  </providers> 
</membership>

2단계: 구성 항목이 추가되면 부록에 제공된 멤버 자격 공급자 코드를 디렉터리에 XmlMembershipProvider.cs%systemdrive%\inetpub\wwwroot\App_Code로 저장해야 합니다. 이 디렉터리가 없으면 만들어야 합니다.

참고

메모장을 사용하는 경우 파일이 XmlMembershipProvider.cs.txt 저장되지 않도록 다른 이름으로 저장: 모든 파일을 설정해야 합니다.

3단계: 남은 것은 실제 자격 증명 저장소입니다. 아래 xml 코드 조각을 디렉터리에 MembershipUsers.xml 파일 %systemdrive%\inetpub\wwwroot\App_Data 로 저장합니다.

참고

메모장을 사용하는 경우 파일이 MembershipUsers.xml.txt 저장되지 않도록 다른 이름으로 저장: 모든 파일을 설정해야 합니다.

<Users>    
    <User>        
        <UserName>Bob</UserName>
        <Password>contoso!</Password>
        <Email>bob@contoso.com</Email>        
    </User>    
    <User>        
        <UserName>Alice</UserName>        
        <Password>contoso!</Password>        
        <Email>alice@contoso.com</Email>        
    </User>    
</Users>

App_Data 디렉터리가 없는 경우 만들어야 합니다.

참고

Windows Server 2003 및 Windows Vista SP1의 보안 변경으로 인해 더 이상 IIS 관리 도구를 사용하여 비GACed 멤버 자격 공급자에 대한 멤버 자격 사용자 계정을 만들 수 없습니다.

이 작업을 완료한 후 IIS 관리 도구로 이동하여 애플리케이션에 대한 사용자를 추가하거나 삭제합니다. "실행..."에서 "INETMGR"을 시작합니다. 메뉴. "기본 웹 사이트"가 표시될 때까지 왼쪽의 트리 뷰에서 "+" 기호를 엽니다. "기본 웹 사이트"를 선택한 다음, 오른쪽으로 이동하고 "보안" 범주를 클릭합니다. 나머지 기능은 ".NET 사용자"를 표시합니다. ".NET 사용자"를 클릭하고 원하는 사용자 계정을 하나 이상 추가합니다.

MembershipUsers.xml 확인하여 새로 만든 사용자를 찾습니다.

로그인 페이지 만들기

양식 인증을 사용하려면 로그인 페이지를 만들어야 합니다. 메모장을 열고(아래 wwwroot 디렉터리에 액세스할 수 있도록 하려면 Programs\Accessories\Notepad 아이콘을 마우스 오른쪽 단추로 클릭하고 "관리자 권한으로 실행"을 클릭하여 관리자 권한으로 실행해야 합니다.) 디렉터리에 login.aspx 파일을 %systemdrive%\inetpub\wwwroot 만듭니다. 참고 - 파일이 login.aspx.txt 저장되지 않도록 다른 이름으로 저장: 모든 파일을 설정해야 합니다. 다음 줄을 붙여넣습니다.

<%@ Page language="c#" %>    
<form id="Form1" runat="server">    
    <asp:LoginStatus runat="server" />        
    <asp:Login runat="server" />    
</form>

권한 부여 규칙이 특정 리소스에 대한 액세스를 거부할 때 리디렉션되는 로그인 페이지입니다.

테스트

인터넷 Explorer 창을 열고 을 요청http://localhost/default.aspx합니다. 처음에 인증되지 않았고 이전에 인증되지 않은 사용자에 대한 액세스를 보류했기 때문에 login.aspx로 리디렉션되는 것을 볼 수 있습니다. MembershipUsers.xml 지정된 사용자 이름/암호 쌍 중 하나를 사용하여 성공적으로 로그인하면 원래 요청된 default.aspx 페이지로 다시 리디렉션됩니다. 그러면 이 페이지에 인증한 현재 시간 및 사용자 ID가 표시됩니다.

이 시점에서 양식 인증, 로그인 컨트롤 및 멤버 자격을 사용하여 사용자 지정 인증 솔루션을 성공적으로 배포했습니다. 이 기능은 IIS 7.0 이상에서는 새로운 기능이 아니며 이전 IIS 릴리스에서 ASP.NET 2.0부터 사용할 수 있습니다.

그러나 문제는 ASP.NET 처리하는 콘텐츠만 보호된다는 것입니다.

브라우저 창을 닫고 다시 열고 를 요청하는 http://localhost/iisstart.htm경우 자격 증명을 묻는 메시지가 표시되지 않습니다. ASP.NET iisstart.htm 같은 정적 파일에 대한 요청에 참여하지 않습니다. 따라서 양식 인증으로 보호할 수 없습니다. 클래식 ASP 페이지, CGI 프로그램, PHP 또는 Perl 스크립트와 동일한 동작이 표시됩니다. 양식 인증은 ASP.NET 기능이며 해당 리소스에 대한 요청 중에는 사용할 수 없습니다.

전체 애플리케이션에 대해 Forms 인증 사용

이 작업에서는 이전 릴리스에서 ASP.NET 제한을 제거하고 전체 애플리케이션에 ASP.NET Forms 인증 및 URL 권한 부여 기능을 사용하도록 설정합니다.

ASP.NET 통합을 활용하려면 통합 모드에서 실행되도록 애플리케이션을 구성해야 합니다. ASP.NET 통합 모드는 애플리케이션 풀별로 구성할 수 있으므로 서로 다른 모드의 ASP.NET 애플리케이션을 동일한 서버에서 나란히 호스팅할 수 있습니다. 애플리케이션이 이미 있는 기본 애플리케이션 풀은 기본적으로 통합 모드를 사용하므로 여기서는 아무 것도 수행할 필요가 없습니다.

그렇다면 이전에 정적 페이지에 액세스하려고 할 때 통합 모드의 이점을 경험하지 못한 이유는 무엇인가요? 대답은 IIS 7.0 이상과 함께 제공되는 모든 ASP.NET 모듈의 기본 설정에 있습니다.

통합 파이프라인 활용

양식 인증 및 URL 권한 부여 모듈을 포함하여 IIS 7.0 이상과 함께 제공되는 모든 관리되는 모듈의 기본 구성은 이러한 모듈이 (ASP.NET) 처리기가 관리하는 콘텐츠에만 적용되도록 사전 조건을 사용합니다. 이전 버전과의 호환성을 위해 수행됩니다.

사전 조건을 제거하여 콘텐츠에 관계없이 애플리케이션에 대한 모든 요청에 대해 원하는 관리형 모듈을 실행합니다. 이는 정적 파일 및 Forms 기반 인증을 사용하는 다른 애플리케이션 콘텐츠를 보호하기 위해 필요합니다.

이렇게 하려면 디렉터리에 있는 %systemdrive%\inetpub\wwwroot 애플리케이션의 web.config 파일을 열고 첫 번째 <구성> 요소 바로 아래에 다음 줄을 붙여넣습니다.

<system.webServer> 
<modules> 
    <remove name="FormsAuthenticationModule" />    
    <add name="FormsAuthenticationModule" type="System.Web.Security.FormsAuthenticationModule" />    
    <remove name="UrlAuthorization" />    
    <add name="UrlAuthorization" type="System.Web.Security.UrlAuthorizationModule" />    
    <remove name="DefaultAuthentication" />    
    <add name="DefaultAuthentication" type="System.Web.Security.DefaultAuthenticationModule" />    
</modules> 
</system.webServer>

이 구성은 사전 조건 없이 모듈 요소를 다시 추가하여 애플리케이션에 대한 모든 요청에 대해 실행할 수 있도록 합니다.

테스트

이전에 입력한 자격 증명이 더 이상 캐시되지 않도록 인터넷 Explorer 모든 인스턴스를 닫습니다. 인터넷 Explorer 열고 다음 URL에서 애플리케이션을 요청합니다.

http://localhost/iisstart.htm

로그인하기 위해 login.aspx 페이지로 리디렉션됩니다.

이전에 사용한 사용자 이름/암호 쌍으로 로그인합니다. 성공적으로 로그인하면 IIS 시작 페이지를 표시하는 원래 리소스로 다시 리디렉션됩니다.

참고

정적 파일을 요청했지만 관리되는 양식 인증 모듈과 URL 권한 부여 모듈은 리소스를 보호하기 위해 서비스를 제공했습니다.

이를 더 자세히 설명하기 위해 클래식 ASP 페이지를 추가하고 Forms 인증을 사용하여 보호합니다.

메모장을 열고(아래 wwwroot 디렉터리에 액세스할 수 있도록 하려면 관리자 권한으로 실행해야 합니다. Programs\Accessories\Notepad 아이콘을 마우스 오른쪽 단추로 클릭하고 "관리자 권한으로 실행"을 클릭)한 다음 디렉터리에 page.asp 파일을 %systemdrive%\inetpub\wwwroot 만들어야 합니다.

참고

메모장을 사용하는 경우 파일이 page.asp.txt 저장되지 않도록 다른 이름으로 저장: 모든 파일을 설정해야 합니다. 아래 줄을 붙여넣습니다.

<% 
for each s in Request.ServerVariables
   Response.Write s & ": "&Request.ServerVariables(s) & VbCrLf
next
%>

모든 인터넷 Explorer 인스턴스를 다시 닫습니다. 그렇지 않으면 자격 증명이 여전히 캐시되고 을 요청http://localhost/page.asp합니다. 로그인 페이지로 다시 리디렉션되고 인증에 성공하면 ASP 페이지를 표시합니다.

축하합니다. 처리기에 관계없이 서버에 대한 모든 요청에 대해 관리되는 서비스를 서버에 성공적으로 추가했습니다.

요약

이 연습에서는 ASP.NET 통합 모드를 활용하여 ASP.NET 페이지뿐만 아니라 전체 애플리케이션에서 강력한 ASP.NET 기능을 사용할 수 있도록 하는 방법을 보여 줍니다.

더 중요한 것은 이제 모든 애플리케이션 콘텐츠에 대해 실행할 수 있고 애플리케이션에 향상된 요청 처리 서비스 집합을 제공하는 친숙한 ASP.NET 2.0 API를 사용하여 새 관리형 모듈을 빌드할 수 있다는 점입니다.

통합 모드를 활용하고 IIS 7 이상에서 ASP.NET 통합을 활용하는 IIS 모듈을 개발하는 방법에 대한 자세한 팁을 보려면 블로그https://www.mvolo.com/를 자유롭게 검사. 또한 HttpRedirection 모듈을 사용하여 애플리케이션에 요청을 리디렉션하고, DirectoryListingModule을 사용하여 IIS 웹 사이트의 멋진 디렉터리 목록을 찾고, IconHandler를 사용하여 ASP.NET 애플리케이션에 예쁜 파일 아이콘 표시를 비롯한 다양한 모듈을 다운로드할 수 있습니다.

부록

이 멤버 자격 공급자는 이 멤버 자격 공급자에 있는 샘플 XML 멤버 자격 공급자를 기반으로 합니다.

이 멤버 자격 공급자를 사용하려면 코드를 디렉터리에 XmlMembershipProvider.cs%systemdrive%\inetpub\wwwroot\App\_Code 로 저장합니다. 이 디렉터리가 없으면 만들어야 합니다. 참고 - 메모장을 사용하는 경우 다른 이름으로 저장: 모든 파일을 설정하여 파일이 XmlMembershipProvider.cs.txt 저장되지 않도록 해야 합니다.

참고

이 멤버 자격 공급자 샘플은 이 데모의 용도로만 사용됩니다. 암호를 안전하게 저장하고 사용자 작업을 감사하는 등 프로덕션 멤버 자격 공급자에 대한 모범 사례 및 보안 요구 사항을 준수하지 않습니다. 애플리케이션에서 이 멤버 자격 공급자를 사용하지 마세요!

using System; 
using System.Xml; 
using System.Collections.Generic; 
using System.Collections.Specialized; 
using System.Configuration.Provider; 
using System.Web.Security; 
using System.Web.Hosting; 
using System.Web.Management; 
using System.Security.Permissions; 
using System.Web; 

public class AspNetReadOnlyXmlMembershipProvider : MembershipProvider 
{ 
    private Dictionary<string, MembershipUser> _Users; 
    private string _XmlFileName; 
                  // MembershipProvider Properties 

    public override string ApplicationName 
    { 
        get { throw new NotSupportedException(); } 
        set { throw new NotSupportedException(); } 
    } 

    public override bool EnablePasswordRetrieval 
    { 
        get { return false; } 
    } 

    public override bool EnablePasswordReset 
    { 
        get { return false; } 
    } 

    public override int MaxInvalidPasswordAttempts 
    { 
        get { throw new NotSupportedException(); } 
    } 

    public override int MinRequiredNonAlphanumericCharacters 
    { 
        get { throw new NotSupportedException(); } 
    } 

    public override int MinRequiredPasswordLength 
    { 
        get { throw new NotSupportedException(); } 
    } 
   
    public override int PasswordAttemptWindow 
    { 
        get { throw new NotSupportedException(); } 
    } 

    public override MembershipPasswordFormat PasswordFormat 
    { 
        get { throw new NotSupportedException(); } 
    } 

    public override string PasswordStrengthRegularExpression 
    { 
        get { throw new NotSupportedException(); } 
    } 
   
    public override bool RequiresQuestionAndAnswer 
    { 
        get { return false; } 
    } 

    public override bool RequiresUniqueEmail 
    { 
        get { throw new NotSupportedException(); } 
    } 
  
   // MembershipProvider Methods 

    public override void Initialize(string name, 
        NameValueCollection config) 
    { 
        // Verify that config isn't null 
        if (config == null) 
            throw new ArgumentNullException("config"); 

        // Assign the provider a default name if it doesn't have one 
        if (String.IsNullOrEmpty(name)) 
            name = "ReadOnlyXmlMembershipProvider"; 
  
        // Add a default "description" attribute to config if the 
        // attribute doesn't exist or is empty 
        if (string.IsNullOrEmpty(config["description"])) 
        { 
            config.Remove("description"); 
            config.Add("description", 
                "Read-only XML membership provider"); 
        } 
  
        // Call the base class's Initialize method 
        base.Initialize(name, config); 
  
        // Initialize _XmlFileName and make sure the path 
        // is app-relative 
        string path = config["xmlFileName"]; 

        if (String.IsNullOrEmpty(path)) 
            path = "~/App_Data/MembershipUsers.xml"; 

        if (!VirtualPathUtility.IsAppRelative(path)) 
            throw new ArgumentException 
                ("xmlFileName must be app-relative"); 

        string fullyQualifiedPath = VirtualPathUtility.Combine 
            (VirtualPathUtility.AppendTrailingSlash 
            (HttpRuntime.AppDomainAppVirtualPath), path); 

        _XmlFileName = HostingEnvironment.MapPath(fullyQualifiedPath); 
        config.Remove("xmlFileName"); 
  
        // Make sure we have permission to read the XML data source and 
        // throw an exception if we don't 
        FileIOPermission permission = 
            new FileIOPermission(FileIOPermissionAccess.Read, 
            _XmlFileName); 
        permission.Demand(); 
  
        // Throw an exception if unrecognized attributes remain 
        if (config.Count > 0) 
        { 
            string attr = config.GetKey(0); 
            if (!String.IsNullOrEmpty(attr)) 
                throw new ProviderException 
                    ("Unrecognized attribute: " + attr); 
        } 
    } 

    public override bool ValidateUser(string username, string password) 
    { 
        // Validate input parameters 
        if (String.IsNullOrEmpty(username) || 
            String.IsNullOrEmpty(password)) 
            return false; 
  
        // Make sure the data source has been loaded 
        ReadMembershipDataStore(); 
  
        // Validate the user name and password 
        MembershipUser user; 

        if (_Users.TryGetValue(username, out user)) 
        { 
            if (user.Comment == password) // Case-sensitive 
            { 
                return true; 
            } 
        } 

        return false; 
    } 
   
    public override MembershipUser GetUser(string username, 
        bool userIsOnline) 
    { 
  
        // Note: This implementation ignores userIsOnline 
        // Validate input parameters 

        if (String.IsNullOrEmpty(username)) 
            return null; 
  
        // Make sure the data source has been loaded 
        ReadMembershipDataStore(); 
  
        // Retrieve the user from the data source 
        MembershipUser user; 

        if (_Users.TryGetValue(username, out user)) 
            return user; 

        return null; 
    } 
   
    public override MembershipUserCollection GetAllUsers(int pageIndex, 
        int pageSize, out int totalRecords) 
    { 
        // Note: This implementation ignores pageIndex and pageSize, 
        // and it doesn't sort the MembershipUser objects returned 
        // Make sure the data source has been loaded 

        ReadMembershipDataStore(); 

        MembershipUserCollection users = 
            new MembershipUserCollection(); 

        foreach (KeyValuePair<string, MembershipUser> pair in _Users) 
            users.Add(pair.Value); 

        totalRecords = users.Count; 

        return users; 
    } 

    public override int GetNumberOfUsersOnline() 
    { 
        throw new NotSupportedException(); 
    } 

    public override bool ChangePassword(string username, 
        string oldPassword, string newPassword) 
    { 
        throw new NotSupportedException(); 
    } 

    public override bool 
        ChangePasswordQuestionAndAnswer(string username, 
        string password, string newPasswordQuestion, 
        string newPasswordAnswer) 
    { 
        throw new NotSupportedException(); 
    } 

    public override MembershipUser CreateUser(string username, 
        string password, string email, string passwordQuestion, 
        string passwordAnswer, bool isApproved, object providerUserKey, 
        out MembershipCreateStatus status) 
    { 
        throw new NotSupportedException(); 
    } 

    public override bool DeleteUser(string username, 
        bool deleteAllRelatedData) 
    { 
        throw new NotSupportedException(); 
    } 

    public override MembershipUserCollection 
        FindUsersByEmail(string emailToMatch, int pageIndex, 
        int pageSize, out int totalRecords) 
    { 
        throw new NotSupportedException(); 
    } 

    public override MembershipUserCollection 
        FindUsersByName(string usernameToMatch, int pageIndex, 
        int pageSize, out int totalRecords) 
    { 
        throw new NotSupportedException(); 
    } 

    public override string GetPassword(string username, string answer) 
    { 
        throw new NotSupportedException(); 
    } 
   
    public override MembershipUser GetUser(object providerUserKey, 
        bool userIsOnline) 
    { 
        throw new NotSupportedException(); 
    } 

    public override string GetUserNameByEmail(string email) 
    { 
        throw new NotSupportedException(); 
    } 
   
    public override string ResetPassword(string username, 
        string answer) 

    { 
        throw new NotSupportedException(); 
    } 
   
    public override bool UnlockUser(string userName) 
    { 
        throw new NotSupportedException(); 
    } 
   
    public override void UpdateUser(MembershipUser user) 
    { 
        throw new NotSupportedException(); 

    } 
   
    // Helper method 

    private void ReadMembershipDataStore() 
    { 
        lock (this) 
        { 
            if (_Users == null) 
            { 
                _Users = new Dictionary<string, MembershipUser> 
                   (16, StringComparer.InvariantCultureIgnoreCase); 
                XmlDocument doc = new XmlDocument(); 
                doc.Load(_XmlFileName); 
                XmlNodeList nodes = doc.GetElementsByTagName("User"); 
  
                foreach (XmlNode node in nodes) 
                { 
                    MembershipUser user = new MembershipUser( 
                        Name,                       // Provider name 
                        node["UserName"].InnerText, // Username 
                        null,                       // providerUserKey 
                        node["Email"].InnerText,    // Email 
                        String.Empty,               // passwordQuestion 
                        node["Password"].InnerText, // Comment 
                        true,                       // isApproved 
                        false,                      // isLockedOut 
                        DateTime.Now,               // creationDate 
                        DateTime.Now,               // lastLoginDate 
                        DateTime.Now,               // lastActivityDate 
                        DateTime.Now,               // lastPasswordChangedDate 
                        new DateTime(1980, 1, 1)    // lastLockoutDate 
                 ); 

                 _Users.Add(user.UserName, user); 

                } 
            } 
        } 
    } 
}