블로그 이미지
* Microsoft MVP - Windows Development 2014 ~ 2019 5ring * LINE : kaki104 * facebook : https://www.facebook.com/kaki104 https://www.facebook.com/groups/w10app/ kaki104

카테고리

List All (551)
Xamarin Forms (4)
Bot Framework (19)
Azure (9)
Windows 10 (35)
Facebook News & Tips (158)
Windows App(Universa.. (83)
Windows 8&8.1 (113)
Windows Phone 8 (42)
Silverlight (37)
HTML5 & MVC4 (16)
WPF (1)
Portable Class Library (2)
Uncategorised Tips a.. (3)
Kinect for Windows (2)
ETC (12)
kaki104 Scrap (4)
App News (11)
Total521,317
Today36
Yesterday108

1. EntityQuery를 이용해서 클라이언트에서 조건을 추가해서 조회하기


//Server side

[Query]
public IQueryable<VisitReservation> GetVisitReservations(int siteid)
{
        var results = from kkk in this.ObjectContext.VisitReservations
                                    .Include("Visitors")

                                    .Include("Visitors.Cards")
                                    .Include("VisitorUsers")
                                    .Include("VisitorPermissions")
                      where kkk.SiteId == siteid
                      orderby kkk.ReservedOn
                      select kkk;

 

        return results;
}

 

//Client side

using System.ServiceModel.DomainServices.Client;

 

DateTime start = new DateTime(this.StartDate.Year, this.StartDate.Month, this.StartDate.Day, 0, 0, 0);
DateTime end = new DateTime(this.EndDate.Year, this.EndDate.Month, this.EndDate.Day, 23, 59, 59);

var query = from kkk in DomainContext.GetVisitReservationsQuery(this.SiteId)
            where kkk.ReservedOn >= start 
                && kkk.ReservedOn <= end
                && (kkk.UserId == User.Id || kkk.ApproverId == User.Id)
            select kkk;

 

this.DomainContext.Load(query, LoadBehavior.RefreshCurrent,
    lo =>
    {
        if (lo.HasError == true)
        {
            lo.MarkErrorAsHandled();
            MessageBox.Show(string.Format(StringTable.FailedToLoadData, lo.Error.Message));
            return;
        }

        this.VisitReservations = new ObservableCollection<VisitReservation>(lo.Entities);
        this.SearchResult = string.Format("[{0} {1}]", StringTable.SearchResults, this.VisitReservations.Count);
    }, null);

 

 

Posted by MVP kaki104

지난번에 만들었던 WP7Host 프로젝트를 업그레이드해서 실버라이트에서 아이디와 비밀번호를 입력 받아서 SQL CE 4.0 sdf파일로 asp.net membership 인증을 하고, 인증이 되었을 때 WCF RIA Service에서 인증 확인을 해서 실행여부를 판단하고, 에러 메시지 처리를 어떻게 하는지 종합적으로 적어 보도록 하겠다.

다루고자하는 내용이 좀 방대한 내용이라, 아주 자세한 설명은 생략 하도록 하겠다. 대신, 참고 포스트들을 잘 참고하면 충분히 만들 수 있을 것이라 생각한다.

0. 연결 포스트
http://kaki104.tistory.com/entry/WCF-RIA-Service-hosting-SOAP-endpoint
http://kaki104.tistory.com/entry/Silverlight-5-WCF-RIA-Service-SQL-CE-40

1. 참고 포스트

1) Authentication in Business Application in Silverlight 4
http://www.c-sharpcorner.com/uploadfile/dpatra/authentication-in-business-application-in-silverlight-4/
비지니스 어플리케이션을 이용해서 인증을 하는 기본적인 방법에 대해서 설명해 놓았다. 한번 해보면 어떤 것인지 이해할 수 있다.

2) ASP.NET Universal Providers 1.0.1
http://nuget.org/packages/System.Web.Providers
ASP.NET 4.0에서 SQL Server 2005부터 SQL Azure까지 모든 공급자를 제공. 즉, SQL CE 4.0 공급자도 포함을 하고 있다. 이 것을 먼저 설치 해야한다. (단, Entity Framework 4.1, Entity Framework 4.2 June CTP 버전을 사용 한다면, 이 공급자 없이도 직접 접근이 가능하나, 만들어서 제공해 주는 것이니 부담 가지지 말고 그냥 설치해서 사용한다.)

3) How to: Enable Authentication in RIA Services
http://msdn.microsoft.com/en-us/library/ee707353%28VS.91%29.aspx
WCF RIA Service를 이용해서 인증 서비스를 사용하는 방법에 대한 자세한 설명이 나온다.

4) Silverlight and WCF RIA Services (5-Authentication)
http://mtaulty.com/CommunityServer/blogs/mike_taultys_blog/archive/2010/07/02/silverlight-and-wcf-ria-services-5-authentication.aspx
진짜 자세하게 설명해 놓았다. 너무 자세한 설명이라..약간 길지만..내용은 좋다.

5) ASP.NET SQL Server Registration Tool (Aspnet_regsql.exe)
http://msdn.microsoft.com/en-us/library/ms229862(v=vs.80).aspx
membership 데이터 베이스를 생성해주는 툴에 대한 사용법이다.(SQL Server 필요)


2. .Net Framework 4.0에서 SQL CE 4.0 파일 바로 접근하기
Entity Model을 사용한 접근 방법이 아닌 직접 SQL CE 4.0 파일에 접근을 해야하기 때문에, 이 부분을 먼저 해결해야 한다.

3. WP7Host.Web

1) Web.config
설치를 하면 Web.config에 몇가지가 추가된다.(자동으로 추가가 되는 부분도 있지만, 수동으로 추가해 주어야 하는 부분도 있으니 참고하기 바란다)

<?xml version="1.0" encoding="utf-8"?>
<!--
  For more information on how to configure your ASP.NET application, please visit
  http://go.microsoft.com/fwlink/?LinkId=169433
  -->
<configuration>
  <!--configSection-->
  <configSections>
    <sectionGroup name="system.serviceModel">
      <section name="domainServices" type="System.ServiceModel.DomainServices.Hosting.DomainServicesSection, System.ServiceModel.DomainServices.Hosting, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" allowDefinition="MachineToApplication" requirePermission="false" />
    </sectionGroup>
  </configSections>
  <!--connectionStrings-->
  <connectionStrings>
    <add name="WP7ModelContainer" connectionString="metadata=res://*/WP7Model.csdl|res://*/WP7Model.ssdl|res://*/WP7Model.msl;provider=System.Data.SqlServerCe.4.0;provider connection string=&quot;Data Source=|DataDirectory|\WP7CE40.sdf&quot;" providerName="System.Data.EntityClient" />
    <add name="DefaultConnection"
         connectionString="Data Source=|DataDirectory|\SqlCeAspnetdb.sdf;"
         providerName="System.Data.SqlServerCe.4.0" />

  </connectionStrings>
  <!--system.web-->
  <system.web>
    <!--httpModules-->
    <httpModules>
      <add name="DomainServiceModule" type="System.ServiceModel.DomainServices.Hosting.DomainServiceHttpModule, System.ServiceModel.DomainServices.Hosting, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
    </httpModules>
    <compilation debug="true" targetFramework="4.0">
      <assemblies>
        <add assembly="System.Data.Entity, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
      </assemblies>
    </compilation>
    <!--membership-->
    <membership defaultProvider="DefaultMembershipProvider">
      <providers>
        <clear />
        <add name="DefaultMembershipProvider" type="System.Web.Providers.DefaultMembershipProvider, System.Web.Providers, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" connectionStringName="DefaultConnection" enablePasswordRetrieval="false" enablePasswordReset="true" requiresQuestionAndAnswer="false" requiresUniqueEmail="false" maxInvalidPasswordAttempts="5" minRequiredPasswordLength="6" minRequiredNonalphanumericCharacters="0" passwordAttemptWindow="10" applicationName="/" />
      </providers>
    </membership>

    <!--rolmanager-->
    <roleManager enabled="true" defaultProvider="DefaultRoleProvider">
      <providers>
        <clear />
        <add name="DefaultRoleProvider" type="System.Web.Providers.DefaultRoleProvider, System.Web.Providers, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" connectionStringName="DefaultConnection" applicationName="/" />
      </providers>
    </roleManager>
    <!--profile-->
    <profile defaultProvider="DefaultProfileProvider">
      <providers>
        <clear />
        <add name="DefaultProfileProvider" type="System.Web.Providers.DefaultProfileProvider, System.Web.Providers, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" connectionStringName="DefaultConnection" applicationName="/" />
      </providers>
      <properties>
        <add name="FriendlyName" />
      </properties>
    </profile>
    <!--authentication 인증관련 부분으로 forms name은 직접 수정해 주어야 한다. -->
    <authentication mode="Forms">
      <forms name=".WP7Host_ASPXAUTH" timeout="2880" />
    </authentication>

    <sessionState mode="InProc" customProvider="DefaultSessionProvider">
      <providers>
        <add name="DefaultSessionProvider" type="System.Web.Providers.DefaultSessionStateProvider, System.Web.Providers, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" connectionStringName="DefaultConnection" applicationName="/" />
      </providers>
    </sessionState>
  </system.web>
  <!--system.webServer-->
  <system.webServer>
    <modules runAllManagedModulesForAllRequests="true">
      <add name="DomainServiceModule" preCondition="managedHandler" type="System.ServiceModel.DomainServices.Hosting.DomainServiceHttpModule, System.ServiceModel.DomainServices.Hosting, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
    </modules>
    <validation validateIntegratedModeConfiguration="false" />
  </system.webServer>
  <!--system.serviceModel-->
  <system.serviceModel>
    <domainServices>
      <endpoints>
        <add name="OData" type="System.ServiceModel.DomainServices.Hosting.ODataEndpointFactory, System.ServiceModel.DomainServices.Hosting.OData, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
        <add name="Soap" type="Microsoft.ServiceModel.DomainServices.Hosting.SoapXmlEndpointFactory, Microsoft.ServiceModel.DomainServices.Hosting, Version=4.0.0.0, Culture=neutral,PublicKeyToken=31bf3856ad364e35" />
        <add name="Json" type="Microsoft.ServiceModel.DomainServices.Hosting.JsonEndpointFactory, Microsoft.ServiceModel.DomainServices.Hosting, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
      </endpoints>
    </domainServices>
    <serviceHostingEnvironment aspNetCompatibilityEnabled="true" multipleSiteBindingsEnabled="true" />
  </system.serviceModel>
  <!--system.data-->
  <system.data>
    <DbProviderFactories>
      <remove invariant="System.Data.SqlServerCe.4.0"/>
      <add name="Microsoft SQL Server Compact Edition Client Data Provider 4.0" invariant="System.Data.SqlServerCe.4.0" description=".NET Framework Data Provider for Microsoft SQL Server Compact Edition Client 4.0" type="System.Data.SqlServerCe.SqlCeProviderFactory, System.Data.SqlServerCe, Version=4.0.0.0, Culture=neutral, PublicKeyToken=89845dcd8080cc91"/>
    </DbProviderFactories>
  </system.data>
</configuration>

2) 인증 관련 추가 사항
여기에서 추가한 파일들은 New Project를 했을 때 BusinessApplication Template 프로젝트를 만들면 생성되는 파일들이다. 단, SqlCeAspnetdb.sdf 파일은 다른 프로젝트에서 사용했던 것을 복사해 왔다.

Services 폴더에
AuthenticationService.cs                          //인증 서비스
UserRegistrationService.cs                      //사용자 등록 서비스

Resources 폴더에
RegisterationDataResources.resx             //사용자 등록시 사용하는 리소스
RegisterationDataResources.Designer.cs 
ValidationErrorResources.Designer.cs      //사용자 등록시 사용하는 데이터 체크 리소스
ValidationErrorResources.resx

Models 폴더에
User.cs                                                //사용자 클래스
RegistrationData.cs                               //등록 데이터 클래스
Shared 폴더에
User.Shared.cs

App_Data 폴더에
SqlCeAspnetdb.sdf                               //인증 db

서버쪽 작업은 이정도면 완료된 것 같다. 각 파일의 내용은 아래 소스를 받은 후 열어서 보기 바란다.

4. WP7Host

1) App.xaml.cs

  public App()
  {
      this.Startup += this.Application_Startup;
      this.Exit += this.Application_Exit;
      this.UnhandledException += this.Application_UnhandledException;

      InitializeComponent();
     
      //웹컨텍스트를 이용해서 인증을 한다.
      WebContext webContext = new WebContext();
      //폼인증 방식 사용
      webContext.Authentication = new FormsAuthentication();
      this.ApplicationLifetimeObjects.Add(webContext);

  }

  private void Application_Startup(object sender, StartupEventArgs e)
  {
      //웹컨텍스트를 리소스로 추가
      this.Resources.Add("WebContext", WebContext.Current);
      //자동으로 로그인 - 이 페이지에 다시 들어왔을 경우 로그인 유저가 있으면 로드함
      WebContext.Current.Authentication.LoadUser(this.Application_UserLoaded, null);

      this.RootVisual = new MainPage();
  }

  private void Application_UserLoaded(LoadUserOperation operation)
  {
      if (operation.HasError == false)
      {}
      else
      {}
  }


....

  private void Application_UnhandledException(object sender, ApplicationUnhandledExceptionEventArgs e)
  {
      // If the app is running outside of the debugger then report the exception using
      // the browser's exception mechanism. On IE this will display it a yellow alert
      // icon in the status bar and Firefox will display a script error.
      if (!System.Diagnostics.Debugger.IsAttached)
      {

          // NOTE: This will allow the application to continue running after an exception has been thrown
          // but not handled.
          // For production applications this error handling should be replaced with something that will
          // report the error to the website and stop the application.
          e.Handled = true;
          //예외 처리
          switch (e.ExceptionObject.GetType().ToString())
          {
              case "DomainOperationException":
              case "DomainException":
                  break;
              default:
                  Deployment.Current.Dispatcher.BeginInvoke(delegate { ReportErrorToDOM(e); });
                  break;
          }

      }
  }

2) MainPageViewModel.cs

  //생성자
  public MainPageViewModel()
  {
      Model = new WP7Model();

      if (!DesignerProperties.IsInDesignTool)
      {
          Observable.FromEventPattern<PropertyChangedEventArgs>(Model, "PropertyChanged")
                    .ObserveOn(Scheduler.ThreadPool)
                    .ObserveOnDispatcher()
                    .Subscribe(prop =>
                    {
                        switch (prop.EventArgs.PropertyName)
                        {
                            case "MessageData":
                                this.MessageData = ((WP7Model)prop.Sender).MessageData;
                                break;
                        }
                    });

          WebContext.Current.Authentication.LoggedIn += new EventHandler<AuthenticationEventArgs>(Authentication_LoggedIn);
          WebContext.Current.Authentication.LoggedOut += new EventHandler<AuthenticationEventArgs>(Authentication_LoggedOut);

      }

      IsLogin = false;
  }

  void Authentication_LoggedOut(object sender, System.ServiceModel.DomainServices.Client.ApplicationServices.AuthenticationEventArgs e)
  {
      Display();
  }

  void Authentication_LoggedIn(object sender, System.ServiceModel.DomainServices.Client.ApplicationServices.AuthenticationEventArgs e)
  {
      Display();
  }


  private ICommand loginCommand;
  /// <summary>
  /// 로그인 커맨드
  /// </summary>
  public ICommand LoginCommand
  {
      get
      {
          if (loginCommand == null)
          {
              loginCommand = new ActionCommand(() =>
              {
                  // 파라미터로 넘겨줄 아이디와 패스워드
                  LoginParameters lp = new LoginParameters(UserId, UserPW, false, null);

                  WebContext.Current.Authentication.Login(lp, load =>
                  {
                      if (load.HasError == false && load.LoginSuccess == true)
                      {
                      }
                      else
                      {
                          MessageBox.Show("로그인 실패");
                      }
                  }, null);   
                    
              }
              );
          }
          return loginCommand;
      }
  }

  private ICommand logoutCommand;
  /// <summary>
  /// 로그아웃 커맨드
  /// </summary>
  public ICommand LogoutCommand
  {
      get
      {
          if (logoutCommand == null)
          {
              logoutCommand = new ActionCommand(() =>
              {
                  WebContext.Current.Authentication.Logout(load =>
                  {
                      //Display();
                  }, null);

              }
              );
          }
          return logoutCommand;
      }
  }

  private void Display()
  {
      if (WebContext.Current.User.IsAuthenticated == true)
      {
          IsLogin = true;
          UserName = WebContext.Current.User.DisplayName;
          MessageData = "로그인에 성공 했습니다.";
      }
      else
      {
          IsLogin = false;
          MessageData = "로그인이 필요 합니다.";
      }

  }

5. 실행 결과 확인
1) 로그인 전

2) 로그인 후


6. 인증 데이터로 WCF RIA Service 제한과 에러 메시지 출력

WP7DomainService.cs

  [RequiresAuthentication(ErrorMessage="로그인이 필요한 서비스 입니다.")]
  [RequiresRole("Administrator", ErrorMessage="관리자만 삭제가 가능합니다.")]

  public void DeleteWP7UriList(WP7UriList wP7UriList)
  {
      if ((wP7UriList.EntityState != EntityState.Detached))
      {
          this.ObjectContext.ObjectStateManager.ChangeObjectState(wP7UriList, EntityState.Deleted);
      }
      else
      {
          this.ObjectContext.WP7UriListSet.Attach(wP7UriList);
          this.ObjectContext.WP7UriListSet.DeleteObject(wP7UriList);
      }

      //히스토리 삭제
      this.ObjectContext.WP7HistorySet
              .Where(p => p.WP7UriListId == wP7UriList.Id)
              .ToList().ForEach(p =>
              {
                  this.ObjectContext.WP7HistorySet.Attach(p);
                  this.ObjectContext.WP7HistorySet.DeleteObject(p);
              });
  }

로그인 하지 않은 상태에서 삭제 버튼 클릭

kaki104 (Basic 권한)로 로그인 한 후에 삭제 버튼 클릭

administrator로 로그인 한 후 삭제 버튼 클릭


6. 중요한 내용은 모두 작성한 것 같다. 더 자세한 사항은 소스에 있는 주석을 참고 하기 바란다.


** 소스 크기 제한 때문에 WP7Host Silverlight 프로젝트의 Bin 폴더의 내용은 모두 삭제한 상태임
Posted by MVP kaki104
이전 포스트에서 만들었던 WCF RIA Service는 기본적으로는 OData endpoint를 사용하는데, 이것을 SOAP endpoint를 사용하도록 변경을 해보자. SOAP으로 서비스를 사용 할 수 있으면 Windows Phone 7.1 에서도 사용이 가능하다.

이전 포스트 
Silverlight 5 & WCF RIA Service & SQL CE 4.0
http://kaki104.tistory.com/62

1. 참고 사이트
How to open a WCF RIA Services application to other type of clients: the SOAP endpoint (3/5)
http://blogs.msdn.com/b/davrous/archive/2010/12/03/how-to-open-a-wcf-ria-services-application-to-other-type-of-clients-the-soap-endpoint-3-5.aspx
그러나, 지금 환경과 약간 다른 점도 있어서 고생을 했다. 이 사이트의 다른 포스트를 보면 JSON endpoint로 변경하는 내용도 있으니 참고 하기 바란다.

2. WCF RIA Services toolkit 설치 필수

3. ASP.NET 프로젝트 레퍼런스에 Microsoft.ServiceModel.DomainServices.Hosting 추가

4. Web.config 추가

<add name="Soap" type="Microsoft.ServiceModel.DomainServices.Hosting.SoapXmlEndpointFactory,
        Microsoft.ServiceModel.DomainServices.Hosting, Version=4.0.0.0, Culture=neutral,
        PublicKeyToken=31bf3856ad364e35" />

5. 빌드 한번 해주고

6. 익스플로 주소 창에서  http://nameofyourserver:port/ClientBin/NameOfYourSolution-Web-NameOfYourDomainService.svc 
입력해서 서비스가 정상 동작하는지 확인 하자.
개발 당시 내 주소는 http://localhost:33113/Services/WP7Host-Web-Service-WP7DomainService.svc 였다.


7. WCR RIA Service host
참고 자료
A Guide to Deploying RIA Services Solutions
http://blogs.msdn.com/b/bradsevertson/archive/2011/02/17/a-guide-to-deploying-ria-services-solutions.aspx

자료를 요약 하면
1) 웹서버에 .Net Framework 4가 설치 되어 있어야함
2) IIS 가 설치 되어 있어야 함(6 이나 7 버전으로)
3) RIA Services를 설치 해야함 - 가장 좋은 방법
(2번째 Bin Deployment라는 것이 있어서 할 수 도 있다고 하는데..음..시도해 보지 않았음
 3번째 GAC Deployment가 있는데 이것도 시도해 보지 않았음)
4) Web.config를 손봐야 한다는 내용
본인은 가상 서버 호스팅을 받고 있는 관계로, 서버에 RIA Service를 직접 설치 하는 방법을 사용

자료에는 없는 추가사항
1) Web Root에 WCF RIA Service를 사용하는 서비스가 존재하면, 그 하위 응용프로그램으로 등록 할 수 없음
-> 이 것 때문에 한 시간 정도 시간을 보냈는데.. Web Root에 있는 응용프로그램을 하위 응용프로그램으로 변경 하고, 동일한 레벨로 응용프로그램 추가해서 서비스 해야함
2) sdf 파일 문제인데, 이 파일이 서버에서 호스팅을 할 때는 IIS_IUSRS에 수정, 쓰기 권한을 주어야 함. 그래야 파일을 열어서 데이터를 수정 할 수 있다. 아래는 권한이 없을 경우 발생하는 오류 메시지이다.

웹 페이지 오류 세부 정보
사용자 에이전트: Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.0; Trident/4.0; SLCC1; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; .NET4.0C; .NET4.0E)
타임스탬프: Fri, 10 Feb 2012 06:37:00 UTC
메시지: Unhandled Error in Silverlight Application Load operation failed for query 'GetWP7UriListSet'. 기본 공급자가 Open에서 실패했습니다. InnerException 메시지: 데이터베이스 파일에 액세스할 수 없습니다. [ 1884,File name = C:WP7ListApp_DataWP7CE40.sdf,SeCreateFile ]   위치: System.Data.EntityClient.EntityConnection.OpenStoreConnectionIf(Boolean openCondition, DbConnection storeConnectionToOpen, DbConnection originalConnection, String exceptionCode, String attemptedOperation, Boolean& closeStoreConnectionOnFailure)
   위치: System.Data.EntityClient.EntityConnection.Open()
   위치: System.Data.Objects.ObjectContext.EnsureConnection()
   위치: System.Data.Objects.ObjectQuery`1.GetResults(Nullable`1 forMergeOption)
   위치: System.Data.Objects.ObjectQuery`1.System.Collections.Generic.IEnumerable<T>.GetEnumerator()
   위치: System.Data.Objects.ObjectQuery`1.GetEnumeratorInternal()
   위치: System.Data.Objects.ObjectQuery.System.Collections.IEnumerable.GetEnumerator()
   위치: System.ServiceModel.DomainServices.Server.DomainService.Enumerate[T](IEnumerable enumerable, Int32 estimatedResultCount)
   위치: System.ServiceModel.DomainServices.Server.DomainService.Query(QueryDescription queryDescription, IEnumerable`1& validationErrors, Int32& totalCount)
   위치: System.ServiceModel.DomainServices.Hosting.QueryProcessor.Process[TEntity](DomainService domainService, DomainOperationEntry queryOperation, Object[] parameters, ServiceQuery serviceQuery, IEnumerable`1& validationErrors, Int32& totalCount)
   위치: System.ServiceModel.DomainServices.Hosting.QueryOperationBehavior`1.QueryOperationInvoker.InvokeCore(Object instance, Object[] inputs, Object[]& outputs)
줄: 1
문자: 1
코드: 0
URI: http://localhost/wp7list/Silverlight.js

IS_IUSERS에게 권한을 주기가 좀 그렇기는 하지만, 다른 처리 방법에 대해서 찾아 보지를 않아서.. 더 좋은 방법이 있다면, 리플로 남겨주기 바란다.

현재 호스트 중인 사이트
http://kaki105.cafe24.com/wp7list/

Posted by MVP kaki104

윈도우 폰 관련 서버 프로그램을 만드는 중으로 중간 단계 산출물을 간단하게 정리를 한번 하려고 한다.

서버 구성
1. 서버 기본 정보

1) Target framework : .NET Framework 4

2 ) References :
    <Reference Include="Microsoft.CSharp" />
    <Reference Include="System.ComponentModel.DataAnnotations" />
    <Reference Include="System.Data.DataSetExtensions" />
    <Reference Include="System.Data.Entity" />
    <Reference Include="System.Runtime.Serialization" />
    <Reference Include="System.Security" />
    <Reference Include="System.ServiceModel.DomainServices.EntityFramework, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
    <Reference Include="System.ServiceModel.DomainServices.Hosting, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL" />
    <Reference Include="System.ServiceModel.DomainServices.Hosting.OData, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
    <Reference Include="System.ServiceModel.DomainServices.Server, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL" />
    <Reference Include="System.Web.DynamicData" />
    <Reference Include="System.Web.Entity" />
    <Reference Include="System.Web.ApplicationServices" />
    <Reference Include="System" />
    <Reference Include="System.Data" />
    <Reference Include="System.Web.Extensions" />
    <Reference Include="System.Drawing" />
    <Reference Include="System.Web" />
    <Reference Include="System.Xml" />
    <Reference Include="System.Configuration" />
    <Reference Include="System.Web.Services" />
    <Reference Include="System.EnterpriseServices" />
    <Reference Include="System.Xml.Linq" />

3) Web.config
<?xml version="1.0" encoding="utf-8"?>
<!--
  For more information on how to configure your ASP.NET application, please visit
  http://go.microsoft.com/fwlink/?LinkId=169433
  -->
<configuration>
  <configSections>
    <sectionGroup name="system.serviceModel">
      <section name="domainServices" type="System.ServiceModel.DomainServices.Hosting.DomainServicesSection, System.ServiceModel.DomainServices.Hosting, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" allowDefinition="MachineToApplication" requirePermission="false" />
    </sectionGroup>
  </configSections>
  <system.webServer>
    <modules runAllManagedModulesForAllRequests="true">
      <add name="DomainServiceModule" preCondition="managedHandler"
        type="System.ServiceModel.DomainServices.Hosting.DomainServiceHttpModule, System.ServiceModel.DomainServices.Hosting, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
    </modules>
    <validation validateIntegratedModeConfiguration="false" />
  </system.webServer>
  <system.web>
    <httpModules>
      <add name="DomainServiceModule" type="System.ServiceModel.DomainServices.Hosting.DomainServiceHttpModule, System.ServiceModel.DomainServices.Hosting, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
    </httpModules>
    <compilation debug="true" targetFramework="4.0">
      <assemblies>
        <add assembly="System.Data.Entity, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
      </assemblies>
    </compilation>
  </system.web>
  <connectionStrings>
    <add name="WP7ModelContainer"          connectionString="metadata=res://*/WP7Model.csdl|res://*/WP7Model.ssdl|res://*/WP7Model.msl;provider=System.Data.SqlServerCe.4.0;provider connection string=&quot;Data Source=|DataDirectory|\WP7CE40.sdf&quot;"
         providerName="System.Data.EntityClient" />
  </connectionStrings>
  <system.serviceModel>
    <domainServices>
      <endpoints>
        <add name="OData" type="System.ServiceModel.DomainServices.Hosting.ODataEndpointFactory, System.ServiceModel.DomainServices.Hosting.OData, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
      </endpoints>
    </domainServices>
    <serviceHostingEnvironment aspNetCompatibilityEnabled="true"
      multipleSiteBindingsEnabled="true" />
  </system.serviceModel>
</configuration>

2. SQL CE 4.0
1) Server Explorer에서 만들어서 App_Data 폴더에 넣어 놓았음
2) Entity Model을 통해서 모델 만들고, 쿼리 생성해서 sdf파일에 연결해서 테이블 생성 -> 잘 동작함
3) .Net Framework 4.0 상태에서 sdf 파일 오픈 정상, 읽기 정상

3. DomainService
1) Entity Model 실행 후 DomainService 추가시 이상한 오류 메세지 박스 하나 뜨는데 무시하고, 넘어가면 정상적으로 테이블 조회 해서 도메인 서비스 만드는 것 정상

2) 수정 사항

WP7DomainService.cs 파일에서 조회를 하는 쿼리를 수정

  /// <summary>
  /// 조회를 하면서 동시에 히스토리 데이터도 조회 한다.
  /// 점진적 로드를 위해 id로 정렬
  /// </summary>
  /// <returns></returns>
  [Query(IsDefault = true)]
  public IQueryable<WP7UriList> GetWP7UriListSet()
  {
      var query = (
                      (ObjectQuery<WP7UriList>)
                          from kkk in this.ObjectContext.WP7UriListSet
                          orderby kkk.Id
                          select kkk
                  ).Include("WP7History");
      //return this.ObjectContext.WP7UriListSet;
      return query.AsQueryable();
  }

metadata파일도 수정

  //연결데이터 함께 조회
  [Include]
  public EntityCollection<WP7History> WP7History { get; set; }

클라이언트 구성(Silverlight 5 RC)
그동안 Silverlight 5 beta 버전만 가지고 작업을 하다가, 이번에 RC를 깔고 처음 만들어 보는 실버라이트 프로젝트 ㅎㅎ

1. 클라이언트 기본 구성
1) Target Silverlight Version : Silverlight 5
2) References
  <Reference Include="Microsoft.Expression.Interactions, Version=5.0.5.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL" />
  <Reference Include="mscorlib" />
  <Reference Include="System.ComponentModel.DataAnnotations, Version=5.0.5.0, Culture=neutral, PublicKeyToken=ddd0da4d3e678217, processorArchitecture=MSIL" />
  <Reference Include="System.Reactive, Version=1.1.11011.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL" />
  <Reference Include="System.Reactive.Providers, Version=1.1.11011.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL" />
  <Reference Include="System.Reactive.Windows.Threading, Version=1.1.11011.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL" />
  <Reference Include="System.Runtime.Serialization" />
  <Reference Include="System.ServiceModel" />
  <Reference Include="System.ServiceModel.DomainServices.Client, Version=2.0.5.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL" />
  <Reference Include="System.ServiceModel.DomainServices.Client.Web, Version=2.0.5.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL" />
  <Reference Include="System.ServiceModel.Web.Extensions, Version=5.0.5.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL" />
  <Reference Include="System.Windows" />
  <Reference Include="system" />
  <Reference Include="System.Core" />
  <Reference Include="System.Net" />
  <Reference Include="System.Windows.Controls, Version=5.0.5.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
  <Reference Include="System.Windows.Controls.Data" />
  <Reference Include="System.Windows.Controls.Data.Input" />
  <Reference Include="System.Windows.Controls.DomainServices, Version=2.0.5.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
  <Reference Include="System.Windows.Controls.Input, Version=5.0.5.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL" />
  <Reference Include="System.Windows.Data" />
  <Reference Include="System.Windows.Interactivity" />
  <Reference Include="System.Xml" />
  <Reference Include="System.Windows.Browser" />

리퍼런스가 좀 만음. 쓸데 없는 것도 한두개 정도 있기는 하지만, 일단은 사용예정인 것 들도 있어서 나중에 정리 할 예정
Rx는 최신버전으로 설치 했음(1.1.11011.0)
나머지 리퍼런스는 대부분 Silverlight 5 SDK에 들어가 있는 것 같음

1) 서버에 정책 파일 만들어 주지 않았는데도, 접속하는데 문제 없음
2) Microsoft Expression Blend Preview for Silverlight5로 작업 문제 없음
- 한가지..유저컨트롤에 템플릿 생성했는데..오류가 한번 났음..음..일단 템플릿이 중요한 부분은 아니라 삭제하고 다시 작업함
2) 간단하게 SilverlightControl1.xaml 페이지 만들어서 테스트를 해봤는데 DataSource에서 드래그 드롭으로 화면에 추가해서, DomainDataSource로 작업하는 것도 정상 동작함
3) Model, ViewModel 추가
4) MainPage.xaml에서 메인 작업

2. WP7Model.cs

  /// <summary>
  /// 조건에 조회 하기
  /// 클라이언트에서 쿼리를 만들어서 서버에서 실행하고 결과 받음
  /// </summary>
  public void Getting(string condition, object sender)
  {
      //client-side query
      EntityQuery<WP7UriList> query = from wp in context.GetWP7UriListSetQuery()
                                      where (condition == null || wp.UserName.Contains(condition))
                                      orderby wp.UserName
                                      select wp;
      context.Load(query,
                  LoadBehavior.RefreshCurrent,
                  load =>
                  {
                      LoadComplet(load);
                  }, sender);

      //여긴 기존 방식 - 서버에 만들어져 있는 쿼리를 그냥 실행
      //context.Load(context.GetWP7UriListSetQuery(),
      //            LoadBehavior.RefreshCurrent,
      //            load =>
      //            {
      //                LoadComplet(load);
      //            }, sender);
  }

  //결과오면 처리하는 부분
  private void LoadComplet(LoadOperation load)
  {
      //마지막 호출한 오브젝트
      LastExecuteSender = load.UserState;
 
      if (load.HasError != true)
      {
          if (load.Entities.Count() > 0)
          {
              //반환된 데이터 타입을 가지고 데이터 구분해서 모델에 있는 컬렉션에 입력
              switch (load.Entities.FirstOrDefault().GetType().Name)
              {
                  case "WP7UriList":
                      WP7UriCollection = new ObservableCollection<WP7UriList>(load.Entities.Cast<WP7UriList>());
                      break;
                  case "WP7History":
                      WP7HistoryCollection = new ObservableCollection<WP7History>(load.Entities.Cast<WP7History>());
                      break;
              }
              MessageData = "조회 작업이 성공 했습니다.";
          }
          else
          {
              MessageData = "조회 결과가 없습니다.";
          }
      }
      else
      {
          MessageData = "조회 작업이 실패 했습니다.";
      }
  }

3. MainPageViewModel.cs

  private ICommand selectCommand;
  /// <summary>
  /// 조회 커맨드
  /// </summary>
  public ICommand SelectCommand
  {
      get
      {
          if (selectCommand == null)
          {
              selectCommand = new ActionCommand(() =>
              {
                  //조회 함수 호출
                  Model.Getting(null, this);
              });
          }
          return selectCommand;
      }
  }

4. MainPage.xaml

    <!--뷰모델 바로 입력해 버림-->
 <UserControl.DataContext>
  <WP7Host_ViewModels:MainPageViewModel/>
 </UserControl.DataContext>

    <!--폼로드시 SelectCommand 바로 실행-->
 <i:Interaction.Triggers>
  <i:EventTrigger>
   <i:InvokeCommandAction Command="{Binding SelectCommand, Mode=OneWay}"/>
  </i:EventTrigger>
 </i:Interaction.Triggers>

5. 실행 화면

 


 

Posted by MVP kaki104

간단한 콤보박스에 바인딩하는 예제를 만들어 보았다. 일단 화면을 보자

WCF RIA 서비스를 이용해서 3개의 데이터를 조회한 후에 상품은 데이터 그리드에 바인딩하고, 오른쪽 각각의 콤보박스에 카테고리, 공급자를 바인딩을 해 놓았다. 그리고 그리드에서 선택된 데이터를 변경하면 콤보박스에 데이터가 변경되고, 콤보박스에서 데이터를 변경하면 그리드의 데이터가 변경되도록 만들어 놓았다.

바인딩의 기초적인 내용인데 처음에 만들려고 하면 좀 까다로운 부분이 있다. 예제를 참고해서 연습하면 바인딩 마스터를 할 수 있을 것이다.

ComboBoxSampleView.xaml

<UserControl x:Class="SL4_RIA_Sample.ComboBoxSampleView"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d"
    d:DesignHeight="300" d:DesignWidth="400"
    xmlns:riaControls="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.DomainServices"
    xmlns:my="clr-namespace:SL4_RIA_Sample.Web"
    xmlns:sdk="http://schemas.microsoft.com/winfx/2006/xaml/presentation/sdk">
   
    <Grid x:Name="LayoutRoot" Background="White">
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="0.5*" />
            <ColumnDefinition Width="0.5*" />
        </Grid.ColumnDefinitions>


        <!--상품 조회용 리아 컨트롤-->

        <riaControls:DomainDataSource AutoLoad="True" Height="0" LoadedData="productDomainDataSource_LoadedData" x:Name="productDomainDataSource" QueryName="GetProductsQuery" Width="0">
            <riaControls:DomainDataSource.DomainContext>
                <my:DomainService1 />
            </riaControls:DomainDataSource.DomainContext>
        </riaControls:DomainDataSource>


        <!--카테고리 조회용 리아컨트롤-->

        <riaControls:DomainDataSource AutoLoad="True" Height="0" LoadedData="categoryDomainDataSource_LoadedData" x:Name="categoryDomainDataSource" QueryName="GetCategoriesQuery" Width="0">
            <riaControls:DomainDataSource.DomainContext>
                <my:DomainService1 />
            </riaControls:DomainDataSource.DomainContext>
        </riaControls:DomainDataSource>


         <!--공급자 조회용 리아컨트롤-->

        <riaControls:DomainDataSource AutoLoad="True" Height="0" LoadedData="supplierDomainDataSource_LoadedData" x:Name="supplierDomainDataSource" QueryName="GetSuppliersQuery" Width="0">
            <riaControls:DomainDataSource.DomainContext>
                <my:DomainService1 />
            </riaControls:DomainDataSource.DomainContext>
        </riaControls:DomainDataSource>


        <!--그리드-->

        <sdk:DataGrid AutoGenerateColumns="False" ItemsSource="{Binding Data, ElementName=productDomainDataSource}" x:Name="productDataGrid" RowDetailsVisibilityMode="VisibleWhenSelected">
            <sdk:DataGrid.Columns>
                <sdk:DataGridTextColumn x:Name="product_NameColumn" Binding="{Binding Product_Name}" Header="Product Name" Width="SizeToHeader" />
             <sdk:DataGridTextColumn x:Name="english_NameColumn" Binding="{Binding English_Name, Mode=TwoWay}" Header="English Name" Width="SizeToHeader" />
                <sdk:DataGridTextColumn x:Name="category_IDColumn" Binding="{Binding Category_ID, Mode=TwoWay}" Header="Category ID" Width="SizeToHeader" />
             <sdk:DataGridTextColumn x:Name="product_IDColumn" Binding="{Binding Product_ID, Mode=OneWay}" Header="Product ID" IsReadOnly="True" Width="SizeToHeader" />
                <sdk:DataGridTextColumn x:Name="supplier_IDColumn" Binding="{Binding Supplier_ID, Mode=TwoWay}" Header="Supplier ID" Width="SizeToHeader" />
            </sdk:DataGrid.Columns>
        </sdk:DataGrid>

 

        <StackPanel Grid.Column="1" Margin="4,0,0,0" >
            <StackPanel Orientation="Horizontal">
                <TextBlock Text="Product Name" Width="90"/>
                <TextBox Text="{Binding SelectedItem.Product_Name, ElementName=productDataGrid, Mode=TwoWay}" />
            </StackPanel>


            <!--카테고리 콤보박스-->

            <StackPanel Orientation="Horizontal">
                <TextBlock Text="Category" Width="90" />
                <ComboBox ItemsSource="{Binding Data, ElementName=categoryDomainDataSource, Mode=OneWay}" DisplayMemberPath="Category_Name" SelectedValuePath="Category_ID" SelectedValue="{Binding SelectedItem.Category_ID, ElementName=productDataGrid, Mode=TwoWay}" />
            </StackPanel>


            <!--공급자 콤보박스-->
            <StackPanel Orientation="Horizontal">
             <TextBlock Text="Supplier" Width="90" />
             <ComboBox ItemsSource="{Binding Data, ElementName=supplierDomainDataSource, Mode=OneWay}" DisplayMemberPath="Company_Name" SelectedValuePath="Supplier_ID" SelectedValue="{Binding SelectedItem.Supplier_ID, ElementName=productDataGrid, Mode=TwoWay}" />
            </StackPanel>
        </StackPanel>
    </Grid>
</UserControl>


소스를 실행하여 분석해보면...
 어떻게 바인딩을 했는지 확인 할 수 있다. 궁금한 점이나 요청사항은 항상 리플로 남겨주면 매우 친절한 설명을 추가하도록 하겠다..^^

Posted by MVP kaki104

지난번 강좌에 사진 촬영에 대한 내용을 다루었다..그러나 사진 촬영만 하면 무었을 하겠는가..어디다가 저장을 해야하는데..물론 실버라이트는 기본적으로 격리저장소(Isolated Storage)를 제공하고 있어서, 처음에는 사진을 촬영해서 그곳에 저장하고 그걸 다시 서버로 전송을 할려는 계획을 세우고 이것 저것 찾아보았는데..

서버로 파일을 업로드할려고 찾았던 컨트롤이 절대 경로를 가지고 파일 정보를 가지고 올 수 있어야지만, 업로드가 가능했던 것이다.하지만, 격리저장소는 OOB 상태가 아니라면 실버라이트4 응용프로그램이 절대 경로를 사용해서는 접근이 불가능하다..(이런 내용을 알기위해 삽질했던 시간이 2틀 정도였던 것 같다..ㅡㅡ;;;)

그렇다면, 사진을 찍어서 로컬에 저장하지 않고, 그냥 서버로 바로 던져서 서버에 저장을 해야겠다는 결론을 내리고 사이트를 검색하다가 찾은 것이 WCF RIA Service를 사용한 파일 업로드 방식이였다. 처음에 그걸 발견하고, 완전 기뻐서 땡큐를 연발하면서 리플을 남겼었다.

강좌를 위해서 소스를 좀 수정하고, 간단하게 목록까지 볼 수 있도록 수정을 했다.

이 화면의 구성은 왼쪽 리스트 박스에서 클릭을 하면 오른쪽에 큰 이미지를 보여 줄려는 의도로 만들었다. 그러나..시간 관계상..해당 기능을 구현하지는 않았다. 여러분들께서 만들어서 올려 줄것이라고 믿는다. ^^ 또..사진 촬영을 2번하면 화면이 먹통이 되는 이상 현상이 있는데..이 부분도 수정해서 완성 올려주면 좋겠다. 그래서 구현되어 있는 기능은

1) 웹켐 촬영

2) 촬영된 이미지를 Jpeg로 변환

3) 변환된 이미지를 WCF RIA Service를 이용해서 서버로 전송

4) 서버에 이미지 저장

5) 저장된 이미지 목록 WCF RIA Service로 조회

6) 조회된 파일 목록을 가지고 리스트박스에 뿌려주면서 실제 이미지를 웹서버에서 불러와서 보여준다.


1. 서버작업

SL4_RIA_Sample.Web 프로젝트에

Assets 폴더를 하나 추가
FileHandler.cs, WebFile.cs를 추가한다.

WebFile.cs

using System.ComponentModel.DataAnnotations;

namespace SL4_RIA_Sample.Web.Assets
{
    //전송될 파일 클래스
    public class WebFile
    {  //WCF RIA Service의 DTO로 사용하기 위해서는 반드시 Key가 필요하다.
        [Key]
        public string FileName { get; set; }
        public byte[] FileContent { get; set; }
    }
}

무지 심플한 클래스다. 이 클래스가 파일을 마치 레코드 다루듯이 가지고 올것이다.

FileHandler.cs

using System.Web;
using System.Web.Configuration;
using System.IO;

namespace SL4_RIA_Sample.Web.Assets
{
    /// <summary>
    /// http://community.infragistics.com/blogs/anton_staykov/archive/2010/04/28/implementing-simple-file-upload-using-silverlight-4-drag-amp-drop-feature-and-infragistics-compression-library.aspx
    /// 이곳에 있던 소스를 참고로 수정
    /// 파일핸들러 스택틱 클래스 웹파일을 로컬(서버)에 저장하는 역할을 한다.
    /// </summary>
    public static class FileHandler
    {
        /// <summary>
        /// 파일전송 함수
        /// </summary>
        /// <param name="file">웹파일 클래스</param>
        public static void HandleFile(WebFile file)
        {
            //업로드 폴더 지정(Web.Config 파일에 절대 경로 존재)
            string uploadDir = WebConfigurationManager.AppSettings["UploadDir"];
            //업로드 폴더가 존재하면
            if (!string.IsNullOrEmpty(uploadDir))
            {
                //업로드 폴더명에 ~/ 이런 글씨가 있으면
                if (uploadDir.IndexOf("~/") == 0)
                    //상대경로를 절대경로로 변경
                    uploadDir = HttpContext.Current.Server.MapPath(uploadDir);
                //업로드 폴더명에 /이것만 있으면
                if (uploadDir.LastIndexOf("/") == uploadDir.Length - 1)
                    //절대경로이니 대충 짤라서 사용
                    uploadDir = uploadDir.Substring(0, uploadDir.Length - 1);

                //저장할 풀경로를 만들고
                string fullFileName = string.Format("{0}/{1}", uploadDir, file.FileName);

                //파일 존재여부 확인
                if (File.Exists(fullFileName))
                {
                    //확장자
                    string ext = fullFileName.Substring(fullFileName.LastIndexOf("."));
                    //파일명
                    string fName = fullFileName.Substring(0, fullFileName.LastIndexOf("."));
                    //풀파일명
                    fullFileName = string.Format("{0}_1{1}", fName, ext);
                }
                //저장~
                File.WriteAllBytes(fullFileName, file.FileContent);
            }
        }
    }
}

위의 소스를 보니 Web.config에 업로드 폴더도 지정해야한다.

Web.config 파일에 추가
  <!--업로드폴더 지정-->
  <appSettings>
    <add key="UploadDir" value="~/UserUploads" />
  </appSettings>

음..그럼 이걸 사용하는 WCF RIA Service를 만들자

FileUploaderDomainService.cs

namespace SL4_RIA_Sample.Web
{
    using System.Collections.Generic;
    using System.IO;
    using System.Linq;
    using System.ServiceModel.DomainServices.Hosting;
    using System.ServiceModel.DomainServices.Server;
    using System.Web;
    using System.Web.Configuration;
    using IDApp.Web.Assets;

    /// <summary>
    /// 파일 업로드용 WCF RIA Service 생성
    /// </summary>
    [EnableClientAccess()]
    public class FileUploaderDomainService : DomainService
    {
        //파일 목록들 조회
        public IQueryable<WebFile> GetFiles()
        {
            //업로드 폴더명 가지고 가서
            string path = WebConfigurationManager.AppSettings["UploadDir"];

            //웹파일 정보 리스트 객체를 만들고,
            List<WebFile> webFiles = new List<WebFile>();

            //폴더에 내용이 없으면 그냥 빈거 던지고
            if (string.IsNullOrEmpty(path))
                return webFiles.AsQueryable();

            //디렉토리 정보를 가지고 오고
            DirectoryInfo di = new DirectoryInfo(HttpContext.Current.Server.MapPath(path));
            //파일정보도 가지고 오고
            foreach (FileInfo file in di.GetFiles())
            {
                //웹파일 정보 리스트에 하나씩 추가하고
                webFiles.Add(new WebFile { FileName = file.Name });
            }
            //결과를 던진다.
            return webFiles.AsQueryable();
        }

        //파일 업로드 함수
        public void InsertFile(WebFile file)
        {
            FileHandler.HandleFile(file);
        }
    }
}

마지막으로 프로젝트에 UserUploads 폴더를 추가해 놓는다.
여기 까지 작업을 하면 서버쪽 작업은 일단 완료된다. 컴파일을 해서 오류가 있는지 확인해 본다. 오류가 없으면 클라이언트쪽 작업을 시작한다.


2. 클라이언트 작업

일단 사진을 촬영하면 BitmapImage으로 만들어 진다. 우린 이걸 Jpeg로 변환해야하는데 이미지 컨버트용 오픈소스 라이브러리를 사용한다.

FluxJpeg 라는 라이브러리(https://github.com/briandonahue/FluxJpeg.Core)
위의 주소를 가면 전체 소스를 Download를 받을 수 있고, 다운로드를 받아서 소스를 VS2010으로 열고, 빌드를 해주면 FJ.Core.dll이란 파일이 만들어 진다. 그 DLL파일을 프로젝트에 추가해서 사용한다. 일단 이 소스에도 해당 Dll파일이 포함되어져 있다. Bin\Debug 폴더를 보면 FJ.Core.dll파일을 찾을 수 있다. 파일을 폴더에 복사해 놓은 후 Add Reference로 해당 dll을 프로젝트 리퍼런스로 추가해 놓는다. 지금 추가한 라이브러리를 편하게 사용하기 위해서 확장메소드 기능을 사용한다.


Extension.cs
이 확장메소드의 소스는 복사해 온것이기 때문에..주석이 없다..

using System.IO;
using System.Windows.Media.Imaging;
using FluxJpeg.Core;
using FluxJpeg.Core.Encoder;

namespace SL4_RIA_Sample
{
    //확장메소드 만들기
    public static class Extension
    {
        public static void EncodeJpeg(this WriteableBitmap bitmap, MemoryStream stream, int quality = 90)
        {
            int width = bitmap.PixelWidth;
            int height = bitmap.PixelHeight;
            int bands = 3;
            byte[][,] raster = new byte[bands][,];
            for (int i = 0; i < bands; i++)
            {
                raster[i] = new byte[width, height];
            }
            for (int row = 0; row < height; row++)
            {
                for (int column = 0; column < width; column++)
                {
                    int pixel = bitmap.Pixels[width * row + column];
                    raster[0][column, row] = (byte)(pixel >> 16); //R
                    raster[1][column, row] = (byte)(pixel >> 8);  //G
                    raster[2][column, row] = (byte)pixel;         //B
                }
            }
            ColorModel model = new ColorModel { colorspace = ColorSpace.RGB };
            FluxJpeg.Core.Image img = new FluxJpeg.Core.Image(model, raster);

            JpegEncoder encoder = new JpegEncoder(img, quality, stream);
            encoder.Encode();

            stream.Seek(0, SeekOrigin.Begin);
        }
    }
}

확장메소드가 무엇인지..어떻게 사용하는지는 잠시 후에 보도록 하겠다.

확장메소드까지 작업을 했다면..사진을 보여주는 화면을 보도록 하자..

PhotoView.xaml

<UserControl
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:SL4_RIA_Sample_Web_DomainService="clr-namespace:SL4_RIA_Sample.Web"
    xmlns:local="clr-namespace:SL4_RIA_Sample" x:Class="SL4_RIA_Sample.PhotoView"
    mc:Ignorable="d" Loaded="UserControl_Loaded"
    d:DesignHeight="300" d:DesignWidth="400">

    <UserControl.Resources>
        <local:ImageFullPathConverter x:Key="ImageFullPathConverter"/>

        <!--리스트박스에서 사용하는 데이터 템플릿-->
        <DataTemplate x:Key="ListDataTemplate">
            <Grid d:DesignWidth="97" d:DesignHeight="99">
                <Grid.RowDefinitions>
                    <RowDefinition Height="0.192*"/>
                    <RowDefinition Height="0.808*"/>
                </Grid.RowDefinitions>
                <TextBlock TextWrapping="Wrap" Text="{Binding FileName}"/>
                <Viewbox Grid.Row="1" HorizontalAlignment="Left">
                    <Image Source="{Binding FileName, Converter={StaticResource ImageFullPathConverter}}"
                           Width="120" Margin="5"/>
                </Viewbox>
            </Grid>
        </DataTemplate>
    </UserControl.Resources>
   
    <Grid x:Name="LayoutRoot" Background="White">
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="177" />
            <ColumnDefinition />
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition Height="38" />
            <RowDefinition Height="261*" />
        </Grid.RowDefinitions>
       
        <Grid.DataContext>

            <!--블랜드에서 바인딩 작업 할려고 추가해 놓았던 것-->
            <SL4_RIA_Sample_Web_DomainService:FileUploaderDomainContext/>
        </Grid.DataContext>

        <!--상단 버튼-->       
        <StackPanel Orientation="Horizontal" Grid.ColumnSpan="2">
            <Button Content="웹켐 촬영" Width="80" Margin="4" Click="Button_Click" />
            <Button Content="이미지 목록" Width="80" Margin="4" Click="Button_Click_1" />
        </StackPanel>

        <!--리스트박스-->
        <ListBox Grid.Row="1"
                 ItemTemplate="{StaticResource ListDataTemplate}"
                 ItemsSource="{Binding WebFiles, Mode=OneWay}"
                 HorizontalContentAlignment="Stretch"
                 VerticalContentAlignment="Stretch"/>
       
    </Grid>
</UserControl>


PhotoView.xaml.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using SL4_RIA_Sample.Web;

 

namespace SL4_RIA_Sample
{
    public partial class PhotoView : UserControl
    {
        //WCF RIA Service 컨텍스트 생성
        FileUploaderDomainContext ctx;

 

        public PhotoView()
        {
            InitializeComponent();
        }

 

        /// <summary>
        /// 촬영시작 버튼
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void Button_Click(object sender, RoutedEventArgs e)
        {
            WebCam_Child webCam = new WebCam_Child();
            webCam.Show();
        }

        /// <summary>
        /// 유저컨트롤 로드 완료 이벤트
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void UserControl_Loaded(object sender, RoutedEventArgs e)
        {

            //초기화
            ctx = new FileUploaderDomainContext();

            //DataContext에 도메인서비스 자체를 넣어놓음 (물론 좋은 방법은 아니니..옵저블 컬렉션을 사용 권고)

            LayoutRoot.DataContext = ctx;
        }

 

        /// <summary>
        /// 이미지 목록 버튼
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void Button_Click_1(object sender, RoutedEventArgs e)
        {
            ctx.Load(ctx.GetFilesQuery(), true);
        }
    }
}

여기까지 작업을 했으면 중요한 한가지가 있다. 리스트박스에 이미지 이름을 바인딩하는데 이미지 이름 바인딩 하면서 바로 ImageFullPathConverter가 필요한데..여기서 하는 일이 중요하다.


ImageFullPathConverter.cs

using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Windows.Data;

 

namespace SL4_RIA_Sample
{
    public class ImageFullPathConverter : IValueConverter
    {

        public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            string fileName = value as string;
            string ReturnValue;

            //호스트 Uri구함
            string hostUri = App.Current.Host.Source.ToString().Replace(App.Current.Host.Source.LocalPath, "");
            ReturnValue = hostUri + "/UserUploads/" + fileName;

            return ReturnValue;
        }


        public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }
}

이미지들은 서버에 존재하기 때문에 서버의 주소를 이미지 이름 앞에 추가해 주는 기능을 한다.

그럼 이제 마지막으로


WebCam_Child.xaml.cs 파일을 수정해 보자


using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.IO;
using SL4_RIA_Sample.Web;
using SL4_RIA_Sample.Web.Assets;
using System.ServiceModel.DomainServices.Client;

namespace SL4_RIA_Sample
{
    public partial class WebCam_Child : ChildWindow
    {
        private CaptureSource _capture;
        //일단 여기다가 저장해놨다가, 성공하면 프로퍼티로 이동
        private string captureFileName;
        //다른 화면에서 저장된 파일명 참고
        public string CaptureFileName;


        public WebCam_Child()
        {
            InitializeComponent();
        }

        private void ChildWindow_Loaded(object sender, RoutedEventArgs e)
        {
            //캡춰소스 인스턴스
            _capture = new CaptureSource();
            _capture.CaptureImageCompleted += new EventHandler<CaptureImageCompletedEventArgs>(_capture_CaptureImageCompleted);
        }

        /// <summary>
        /// 이미지 캡처 완료되었을 때 발생하는 이벤트
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void _capture_CaptureImageCompleted(object sender, CaptureImageCompletedEventArgs e)
        {
            //이미지 캡춰가 완료되면 여기로
            WriteableBitmap wb = e.Result;
            //화면에 촬영된 이미지 보여주기
            if (_capture != null)
            {
                _capture.Stop();
                ImageBrush image = new ImageBrush();
                image.ImageSource = wb;
                rectVideo.Fill = image;
            }

            //파일명 생성
            string name = Guid.NewGuid().ToString() + ".bmp";
            //메모리 스트림 인스턴스
            MemoryStream stream = new MemoryStream();

            //확장메소드를 이용해서 이미지를 jpeg 포멧으로 변환
            wb.EncodeJpeg(stream);

            //WCF RIA Service 컨텍스트 생성
            FileUploaderDomainContext ctx = new FileUploaderDomainContext();
            //웹파일 정보 하나 만들고
            WebFile file = new WebFile { FileName = name };
            //파일 컨텐츠에 메모리 스트림의 버퍼데이터를 쓸어 넣는다.
            //메모리 스트림아니라 파일 스트림의 데이터도 넣을 수 있다.
            file.FileContent = stream.GetBuffer();
            //웹파일스에..방금 만든거 추가..마치 레코드 추가하듯이
            ctx.WebFiles.Add(file);
            //그리고 세이브체인지 호출~
            ctx.SubmitChanges(SubmitCallBack, null);
            //전송한 파일 이름을 저장해 놓고
            captureFileName = name;
        }

        /// <summary>
        /// 세이브체인지 완료 콜백함수
        /// </summary>
        /// <param name="op"></param>
        private void SubmitCallBack(SubmitOperation op)
        {
            //에러가 있으면 에러 보여주고 끝~
            if (op.HasError)
            {
                MessageBox.Show(op.Error.Message);
                this.DialogResult = false;
                CaptureFileName = "";
            }
            else
            { //여긴 저장 완료~
                MessageBox.Show("작업을 완료했습니다.");
                this.DialogResult = true;
                CaptureFileName = captureFileName;
            }
            //팝업 화면 닫기
            this.Close();
        }

        private void OKButton_Click(object sender, RoutedEventArgs e)
        {
            //캡춰 시작
            _capture.CaptureImageAsync();
        }

        private void CancelButton_Click(object sender, RoutedEventArgs e)
        {
            this.DialogResult = false;
        }

        private void StartButton_Click(object sender, RoutedEventArgs e)
        {
            Button btn = ((Button)sender);

            if (_capture != null)
            {
                //일단 동작 정지
                _capture.Stop();

                if (btn.Content.ToString() == "촬영시작")
                {
                    //기본 비디오 디바이스를 가지고옴
                    _capture.VideoCaptureDevice = CaptureDeviceConfiguration.GetDefaultVideoCaptureDevice();
                    //기본 오디오 디바이스를 가지고옴
                    _capture.AudioCaptureDevice = CaptureDeviceConfiguration.GetDefaultAudioCaptureDevice();

                    //비디오 브러쉬 만들고
                    VideoBrush videoBrush = new VideoBrush();
                    videoBrush.Stretch = Stretch.Uniform;
                    videoBrush.SetSource(_capture);
                    rectVideo.Fill = videoBrush;
                    // request user permission and display the capture
                    if (CaptureDeviceConfiguration.AllowedDeviceAccess || CaptureDeviceConfiguration.RequestDeviceAccess())
                    {
                        _capture.Start();
                        btn.Content = "촬영종료";
                    }
                }
                else
                {
                    btn.Content = "촬영시작";
                }
            }
        }
    }
}

 

3. 간단하게 만들려고 했는데..
일이 커져버렸다..;; 배보다 배꼽이 더 커진..흐흐..서버로 파일을 업로드 하는 방법도 이렇게 사용해서 올릴 수 있다. 어떤 파일이라도 스트림형태로 읽어 들인다면, 그 스트림의 버퍼를 WebFile 클래스에 쑤셔 넣어서 SubmitChange()만 호출 한다면 자동으로 올라간다..앞으로 실버라이트에서 WCF RIA Service를 사용한 완성된 파일 업로드 기능을 기대하며 이번 강좌를 마친다.

Posted by MVP kaki104

실버라이트4.0 부터 기본적으로 웹켐과 오디오를 기본으로 사용할 수 있는 기능이 추가되어있다.
이 기능을 이용하는 방법을 알아보도록 하자..그런데, 왜 뜬금없이 웹켐에 대한 이야기를 하는 것일까? 다음 강좌에서 촬영한 사진을 WCF RIA Service로 서버로 전송하는 것을 올릴려고 하기 때문이다.


1. 화면보기

일단 기존 화면에 버튼을 하나 추가하고 버튼을 클릭하면 차일드 윈도우를 출력하고, 그 곳에서 웹켐 활성화를 시키고 사진 촬영을 하게 된다. 촬영시작을 누르면 위의 화면 처럼 실버라이트에서 카메라와 오디오에 엑세스를 허용할 것인지를 물어본다.(이렇게 나오도록 만든다고..몇시간 삽질을....나만 어렵게 적용했다는 생각이..쿨럭;;)


WebCam_Child.xaml

<controls:ChildWindow x:Class="SL4_RIA_Sample.WebCam_Child"
           xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
           xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
           xmlns:controls="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls"
           Width="400" Height="300"
           Title="WebCam_Child" Loaded="ChildWindow_Loaded">
    <Grid x:Name="LayoutRoot" Margin="2">
        <Grid.RowDefinitions>
            <RowDefinition />
            <RowDefinition Height="Auto" />
        </Grid.RowDefinitions>

        <!--이미지가 보이는 곳~-->
        <Rectangle Grid.Row="0" Name="rectVideo" Fill="Black" Margin="4" />

        <Button x:Name="CancelButton" Content="닫기" Click="CancelButton_Click" Width="75" Height="23" HorizontalAlignment="Right" Margin="0,12,0,0" Grid.Row="1" />
        <Button x:Name="OKButton" Content="촬영" Click="OKButton_Click" Width="75" Height="23" HorizontalAlignment="Right" Margin="0,12,79,0" Grid.Row="1" />
        <Button x:Name="StartButton" Content="촬영시작" Click="StartButton_Click" Width="75" Height="23" HorizontalAlignment="Right" Margin="0,12,158,0" Grid.Row="1" />
    </Grid>
</controls:ChildWindow>


WebCam_Child.xaml.cs

using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Imaging;

namespace SL4_RIA_Sample
{
    public partial class WebCam_Child : ChildWindow
    {
        private CaptureSource _capture;

        public WebCam_Child()
        {
            InitializeComponent();
        }

        private void ChildWindow_Loaded(object sender, RoutedEventArgs e)
        {
            //캡춰소스 인스턴스
            _capture = new CaptureSource();
            _capture.CaptureImageCompleted += new EventHandler<CaptureImageCompletedEventArgs>(_capture_CaptureImageCompleted);
        }

        void _capture_CaptureImageCompleted(object sender, CaptureImageCompletedEventArgs e)
        {
            //이미지 캡춰가 완료되면 여기로
            WriteableBitmap wb = e.Result;
            //화면에 촬영된 이미지 보여주기
            if (_capture != null)
            {
                _capture.Stop();
                ImageBrush image = new ImageBrush();
                image.ImageSource = wb;
                rectVideo.Fill = image;
            }
        }

        private void OKButton_Click(object sender, RoutedEventArgs e)
        {
            //캡춰 시작
            _capture.CaptureImageAsync();
            //this.DialogResult = true;
        }

        private void CancelButton_Click(object sender, RoutedEventArgs e)
        {
            this.DialogResult = false;
        }

        private void StartButton_Click(object sender, RoutedEventArgs e)
        {
            Button btn = ((Button)sender);

            if (_capture != null)
            {
                //일단 동작 정지
                _capture.Stop();

                if (btn.Content.ToString() == "촬영시작")
                {
                    //기본 비디오 디바이스를 가지고옴
                    _capture.VideoCaptureDevice = CaptureDeviceConfiguration.GetDefaultVideoCaptureDevice();
                    //기본 오디오 디바이스를 가지고옴
                    _capture.AudioCaptureDevice = CaptureDeviceConfiguration.GetDefaultAudioCaptureDevice();

                    //비디오 브러쉬 만들고
                    VideoBrush videoBrush = new VideoBrush();
                    videoBrush.Stretch = Stretch.Uniform;
                    videoBrush.SetSource(_capture);

                    //xaml에 있는 사각형을 비디오브러쉬로 채운다
                    rectVideo.Fill = videoBrush;
                    // 사용자에게 인증을 받는 부분 - 이 부분은 폼로드 이벤트에 넣치 않는다. 별도의 이벤트 함수에서 처리하도록

                    // 해야 한다, 않그러면, 사용자에게 인증 받는 창이 뜨지를 안아서 한참을 헤메는 사태가 발생한다는..

                    if (CaptureDeviceConfiguration.AllowedDeviceAccess || CaptureDeviceConfiguration.RequestDeviceAccess())
                    {
                        _capture.Start();
                        btn.Content = "촬영종료";
                    }
                }
                else
                {
                    btn.Content = "촬영시작";
                }
            }
        }
    }
}

2. 다음에는
이 소스에 추가해서 촬영한 사진을 업로드 하는 것에 대해서 적어보도록 하겠다.

Posted by MVP kaki104

간단하게 한가지만 추가할려고 한다.

대량의 데이터를 한번에 불러오는 경우 불러오는데 시간이 오래 걸리고, 그 데이터가 불러오기 전까지는 화면에 출력되는 내용이 없다. 그래서, 주로 사용하는 방식이 페이징 방식인데.. 페이징 방식의 경우에는 모든 데이터를 한번에 볼 수 없기 때문에..울나라 사람들이 조아하는 엑셀로 한방에 출력하기시에 문제가 있다..

그런 문제의 해답으로 점진적 로드 방식이 있는데, 한번에 20-40개 정도의 로우만을 여러번 불러오는 것이다. 그럼 일단 화면에 데이터가 출력되어있기 때문에 프로그램이 느리다는 이야기는 듣지 않을 것이고, 좀 시간을 두고 기다리면 모든 데이터가 어느세 꽉~ 차고, 마음도 한결 가벼워 질 것이다.


1. DomainService1.cs 수정
일단 WCF RIA에 함수를 하나 추가한다. 조건을 인수로 받아서 해당대는 데이터를 ID 순으로 소트해서 반환하는 함수이다.
여기서 중요한 사항은 꼭 orderby문이 포함되어 있어야 한다는 것이다.

//날짜를 인수로 받아서 해당 조건에 만족하는 데이터만 반환한다.
public IQueryable<Order> GetOrders_By_Condition(DateTime FromDate, DateTime ToDate)
{
    var query = from order in this.ObjectContext.Orders
                where order.Order_Date >= FromDate && order.Order_Date <= ToDate
                orderby order.Order_ID
                select order;

    return query.AsQueryable();
}

작성이 완료되었으면 웹 프로젝트를 빌드한다. 빌드를 해줘야 추가된 함수를 실버라이트에서 사용 할 수 있다.


2. SL4_RIA_Sample 프로젝트에 Silverlight User Control을 하나 추가한다.
이름은 Orders.xaml로 한다.
Data Sources 메뉴를 클릭한다.

Order에 콤보박스 다운 버튼을 클릭하면 위와 같이 나온다.(한번에 앙나오면 다시 컴파일 하고 다시 열어본다) 여기서 GetOrders_By_ConditionQuery를 선택하고 드레그 드롭으로 Orders.xaml에 컨트롤을 만들고, 약간의 작업을 더해주어 아래와 같은 화면을 만든다.

일단 컨트롤을 만들어서 화면에 출력될 수 있는 기본은 만들었고, 실버라이트가 시작되었을 때 맨 처음으로 이 화면을 사용하도록 수정한다.

App.xaml.cs
private void Application_Startup(object sender, StartupEventArgs e)
{
     this.RootVisual = new Orders();
}

실행해보자

데이터가 1991년 부터 1995년까지 있기 때문에..1년간의 날짜를 입력한 후 Load버튼을 눌러서 조회를 해봤다.
모든 데이터가 한번에 쫘악 펼쳐지며 보인다. 아주 만은 데이터는 아니라서 빠르게 표시가 된다. 그러나 날자를 1995년까지로 변경하고 조회하면 처음에 화면 뜨는 속도가 무척 느리다는 것을 알 수 있다.


3. 점진적 로드 방식으로 변경

Orders.xaml

//WCF RIA Service 데이터 컬렉션을 로드, 필터링, 그룹화 및 정렬하기 위한 개체

//http://msdn.microsoft.com/ko-kr/library/system.windows.controls.domaindatasource_members(v=vs.91).aspx


<riaControls:DomainDataSource AutoLoad="False"
                              d:DesignData="{d:DesignInstance my:Order, CreateList=true}" Height="0"
                              LoadedData="orderDomainDataSource_LoadedData"
                              Name="orderDomainDataSource" LoadSize="40" LoadInterval="00:00:04"
                              QueryName="GetOrders_By_ConditionQuery"
                              Width="0" Margin="0,0,400,29">
    <riaControls:DomainDataSource.DomainContext>
        <my:DomainService1 />
    </riaControls:DomainDataSource.DomainContext>
    <riaControls:DomainDataSource.QueryParameters>
        <riaControls:Parameter ParameterName="FromDate" Value="{Binding ElementName=fromDateTextBox, Path=Text}" />
        <riaControls:Parameter ParameterName="ToDate" Value="{Binding ElementName=toDateTextBox, Path=Text}" />
    </riaControls:DomainDataSource.QueryParameters>
</riaControls:DomainDataSource>

위의 LoadSize와 LoadInterval을 이용하면 한번에 몇개씩 몇 초후에 가지고 오는 지를 지정해서 순서대로 데이터를 가지고 올 수 있다.

(시작 - 스크롤 바의 크기를 보면 된다.)

(로딩 중 - 스크롤 바의 크기가 줄어 들었다)



4. WCF로 전송되는 데이터량 늘리기
한번에 전송되는 양이 생각보다 적어서, 좀더 늘릴 필요가 있을 때 사용한다.

Web.config

  <system.serviceModel>
    <domainServices>
      <endpoints>
        <add name="OData" type="System.ServiceModel.DomainServices.Hosting.ODataEndpointFactory, System.ServiceModel.DomainServices.Hosting.OData, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
      </endpoints>
    </domainServices>
    <behaviors>
      <serviceBehaviors>
        <behavior name="">
          <serviceMetadata httpGetEnabled="True" />
          <serviceDebug includeExceptionDetailInFaults="True" />
          <dataContractSerializer maxItemsInObjectGraph="655360" />
        </behavior>
      </serviceBehaviors>
    </behaviors>
    <serviceHostingEnvironment aspNetCompatibilityEnabled="true" multipleSiteBindingsEnabled="true" />
  </system.serviceModel>


5. 실버라이트는
스트림라인드 오퍼레이션이 기본이기 때문에, 모든 데이터를 한번에 다 불러와야 보여지는 그런 화면은 좋은 화면이 아니다. 개발시에는 꼭 참고 하기 바란다. 그리고, 추가 강의 요청이 없어서..그냥 생각 날때 마다 하나씩 올려 놓을려고 한다.(요청은 환영)

Posted by MVP kaki104

지난번에 이어서 매우 간단한 CRUD 하는 방법을 설명하려고 한다. 최대한 간단하게 하기 위해서 타이틀리 커플드(tightly coupled)로 코딩을 하는 걸로 정했다. 나중에 문의가 들어온다면 MVVM을 적용한 루즐리 커플드(loosely coupled) 방식으로 변경하도록 하겠다.


1. 디자인 변경
화면의 상단에 공간을 좀 주고, 그 곳에 버튼을 3개 배치했다. 버튼의 이름은 Add, Update, Remove이다.


디자인을 변경하면 거의 50%는 한 셈이다. 이제 각 각의 버튼에 약간의 코딩을 하도록 하겠다.


2. CRUD 코딩하기
버튼을 더블클릭해서 이벤트를 만들어보자. Button_Click이란 이름의 이벤트가 하나 만들어지면 그걸 나머지 2개의 버튼에도 같이 붙여버린다. 그리고 코딩을 살짝한다.

using SL4_RIA_Sample.Web;


private void Button_Click(object sender, RoutedEventArgs e)
{
    Button btn = (Button)sender;
    //Order는 DTO(Data Transfer Object)로 WCF RIA에서 자동으로 만들어준 것이다. 이걸 사용하려면

    //SL4_RIA_Sample.Web를 써주기만 하면 된다.           

    Order order;

    switch (btn.Content.ToString())
    {
        case "Add":

            //새로운 레코드를 추가하기 위해서는 그냥 새로운 order를 생성해서 추가한 후에 SubmitChanges()만 호출하면 된다.
            order = new Order();
            orderDomainDataSource.DataView.Add(order);
            break;
        case "Update":

            //SubmitChanges()가 실행되면 Insert, Update, Delete가 모두 적용된다.

            //RejectChanges()는 지금까지 수정된 사항을 모두 취소한다.
            orderDomainDataSource.DomainContext.SubmitChanges();
            break;
        case "Remove":

            //삭제는 단지 DataView에서 삭제하려는 Order를 지우기만 하면 된다.
            if (orderDomainDataSource.DataView.CurrentItem != null)
            {
                order = orderDomainDataSource.DataView.CurrentItem as Order;
                orderDomainDataSource.DataView.Remove(order);
            }
            break;
    }
}

3. 변경된 내용 조회
리플로 문의 주신 내용인데.. 데이터베이스가 내가 변경하지 않고 다른 사람이나 프로시저에서 변경된 경우에 새로 조회를 해도 변경된 내용이 바로 반영이 앙된다는 것인데.. 이것은 LoadBehavior에 대한 처리를 해주면 쉽게 해결 할 수 있다.

WCF RIA Control은 LoadBehavior를 기본적으로 KeepCurrent로 지정되어있다.

LoadBehavior는 데이터를 로드했을때 현재 내가 가지고 있는 데이터와 새로 불러온 데이터를 어떻게 처리 할지를 지정하는 것으로 KeepCurrent(현재 가지고 있는 데이터 유지), MergeIntoCurrent(현재 가지고 있는 데이터를 새로 가지고 온 데이터와 머지), RefreshCurrent(현재 데이터를 새로운 데이터로 몽땅 교체)의 3가지 방법을 제공한다.

이 LoadBehavior를 지정하기 위해서는 몇가지 방법이 있는데, 현재 소스에는 DomainDataSource라는 컨트롤을 사용하고 있고 그 컨트롤에서 지정 방법은 LoadingData 이벤트를 추가하고,

private void orderDomainDataSource_LoadingData(object sender, LoadingDataEventArgs e)
{
    e.LoadBehavior = System.ServiceModel.DomainServices.Client.LoadBehavior.RefreshCurrent;
}

위와 같이 코딩을 해주면 된다.

4. 이번에는 여기 까지만 올리겠다.
추가로 요청이 올라오면 더 자세한 강좌를 올리도록 하겠다.


Posted by MVP kaki104

실버라이트 4에 대한 기본적인 사항을 먼저 한번 쭈욱 정리를 하고 실버라이트 5로 넘어가야 할 것 같아서, 일단 초 간단 예제를 만들었다. 이 예제에 사용된 기술은 딱 2가지이다. 실버라이트4와 WCF RIA Service,

WCF RIA Service 설치는 http://www.silverlight.net/getstarted/riaservices/
이 페이지에 가면 3개의 링크가 있고, 3개를 모두 다운로드 받은 후 설치하면 된다.
WCF RIA가 올해 초까지만 해도 참 좋았는데..으흠..머 아직도 좋다..하하;;


1. 전체 레이아웃
1-1. 실버라이트 프로젝을 만든다.
1-2. 서버에 엔티티 프레임웍을 붙인다.
1-3. 데이터베이스를 연결한다.
1-4. 테이블에 기초한 엔티티를 만든다.
1-5. 컴파일 한다
1-6. WCF RIA(Domain Service)를 추가한다.
1-7. 컴파일 한다.
1-8. MainPage.xaml을 열어서 만들어진 컨텍스트를 드래그 드롭한다.
1-9. 실행한다.

무지 간단하다. 아마 한 10분이면 똑같이 만들 수 있을 것이다. 하지만 기능은 막강하다
물론 진짜 프로젝트에서 사용할려면 여러가지 추가적인 작업들이 들어가지만 그래도 좋다.
일단 만들어보고 더 필요한 부분이 있으면 요청이 있는 부분에 대해서만 추가 작업을 해서 올리겠다.


2. 실버라이트 프로젝을 만든다.
만드는 부분은 생략한다.

이름은 간단하게 정했다. 중요한 부분은 아래에 있는 Enable WCF RIA Services를 꼭 체크 해야한다는 것이다.


3. 서버에 엔티티 프레임웍을 붙인다.
서버 프로젝에서 Add -> New Item을 선택해서 Entity Data Model을 추가한다.

이름은 간단하게 기본 이름을 사용했다.


4. 데이터베이스를 연결한다.
기존 데이터베이스에 연결을 해서 모델을 만들 수 있는 기능을 선택한다.

여기서는 Compact 데이터베이스 파일을 사용한다. Compact 4.0을 설치하면 셈플DB인 Northwid.sdf 파일이 존재한다.
꼭 이 파일이 아니더라도 기존에 가지고 있던 데이터베이스 파일도 사용 가능하다.

여기서 Northwid.sdf파일을 선택하고 Next~

선택한 데이터베이스에서 테이블, 뷰, 프로시저 목록을 보여주고 자동으로 모델을 만들 수 있다. 여기서는 공짜니까 모든 테이블을 선택한다.
추가적으로 Pluralize or sigularize generated object names라는 것도 체크해준다. 그리고 finish

위의 그림이 자동으로 모델을 만들어 준 그림이다. 일단 여기까지 되었으면 꼭 F6키를 눌러서 컴파일을 해준다.


5. WCF RIA Service(Domain Service)를 추가한다

웹 프로젝트에서 Add -> New Item -> domain이란 이름으로 검색하면 Domain Service라는 것이 나온다. 이것이 바로 WCF RIA Service이다.

Add버튼을 누르면

위와 같은 화면이 나온다. 역시 공짜니까 다 선택한다. 오른쪽 체크 박스는 CRUD를 하겠냐는 것이다. 나중에 예제를 위해서라도 그냥 체크 하자 중요한 부분은 Generate associated classes for metadata에 체크를 해준다는 것이다.


여기까지 했을 때 완성된 솔루션 익스플로러의 모습이다.

또 다시 F6키를 눌러서 빌드를 하자


6. MainPage.xaml을 열어보자.
그리고 왼쪽으로 보면 Data Sources라는 것이 보이게 된다. (뜨는데 시간이 좀 걸린다)
이것이 WCF RIA Service를 사용 했을 때만 나오는 것으로 서버에서 만들어진 Context를 그냥 바로 드래그 드롭으로 사용 할 수 있도록 만들어 주는 것이다.

위의 내용 중에서 Order라는 것을 드래그 드롭으로 MainPage.xaml 위에 올려 놓고 위치를 조정해서 이쁘게 만든다.

그냥 올려다 놓기만해두 모든 필드의 내용들이 자동으로 데이터 그리드로 만들어진다. 얼마나 멋진 모습인지 흐흐

이제 마지막으로 실행 해보자 F5키를 눌러서 내용이 뜨는지를 보자

맨 처음에는 데이터가 뜨는데 시간이 약간 걸린다. 생각보다 Order테이블에 데이터가 좀 있다. 어차피 비동기 방식으로 가지고 오는 것이기 때문에 약간 기다리면 다 가지고 온다.


7. 초간단 프로젝트를 하나 만들었다.
하지만 여기서 조금만 더 손보면 바로 멋진 프로그램이 된다.
추가적으로 궁금한 사항, 추가 기능 등등을 많이 적어 주면 하나씩 기능을 보강해 보도록 하겠다.

Posted by MVP kaki104

티스토리 툴바