블로그 이미지
* 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)N
Xamarin Forms (4)
Bot Framework (19)N
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,821
Today18
Yesterday92

WebAPI를 사용하려면 Authentication을 하는 방법에 대해서도 알아야 한다. 그래서 해당 내용을 찾았는데.. 설명이 너무 추상적으로 되어 있어서, 좀더 자세하게 c#으로 만들어 보았다. 역시나 PCLSample에 추가를 하고 테스트를 한 내용이다.

 

참고 포스트

Individual Accounts in ASP.NET Web API

http://www.asp.net/web-api/overview/security/individual-accounts-in-web-api

 

인증 방법은 크게

1) Individual Accounts in ASP.NET Web API

2) Forms Authentication

3) Integrated Windows Authentication

이렇게 3가지 정도 인것 같다. 여기서는 1번에 대해서만 다루도록 하겠다.

 

1. Create the Web API Project

 

File -> New Project -> Web -> ASP .NET Web Application -> 이름 입력 -> OK

이 포스트에서 사용할 프로젝트의 이름은 PCLSample.WebAPISecurity 이다. 아래 이미지는 참고 포스트의 있는 것을 그래도 사용한다.

 

Web API 템플릿 선택

 

Change Authentication 클릭 -> Individual User Accounts 선택

 

프로젝트가 생성이 되면 F5를 눌러서 실행해 본다.

 

위와 같이 따라 하면 기본적인 WebAPI 프로젝트가 생성이되고, 서비스를 추가할 수 있게 된다.

 

2. Send an HTTP Request to the Web API

참고 포스트에서는 Fiddler를 사용하여 직접 서버에 Request를 날려서 작업을 하는데, 여기서는 c# 코드로 작업된 내용을 보도록 하자.

 

PCLSample.PCL -> AccountHelper.cs

BaseUri : "http://localhost:22926"

 

        public async Task<string> GetValueTest()
        {
            var returnValue = string.Empty;

            using (var http = new HttpClient())
            {
                var uri = string.Format("{0}{1}", BaseUri, "/api/values");
                var resp = await http.GetStringAsync(uri);
                if (string.IsNullOrEmpty(resp) == false)
                {
                    returnValue = resp;
                }
            }
            return returnValue;
        }

 

위와 같이 호출하면 포스트에 있는 내용과 비슷하게 호출한 것이다.

 

/api/values를 찾아 보도록 하자.

새로운 프로젝트가 템플릿을 이용해서 생성될 때 테스트 용으로 등록된 WebAPI이다.

 

    [Authorize]
    public class ValuesController : ApiController
    {
        // GET api/values
        public IEnumerable<string> Get()
        {
            return new string[] { "value1", "value2" };
        }

        // GET api/values/5
        public string Get(int id)
        {
            return "value";
        }

        // POST api/values
        public void Post([FromBody]string value)
        {
        }

        // PUT api/values/5
        public void Put(int id, [FromBody]string value)
        {
        }

        // DELETE api/values/5
        public void Delete(int id)
        {
        }
    }

 

컨트롤러의 위에 [Authorize]를 추가한 것은, 해당 컨트롤러는 인증된 사용자만이 사용할 수 있다는 것을 의미한다.

그래서 GetValueTest()를 실행해서 데이터를 요청하면, 인증이 되어있지 않기 때문에 결과를 받을 수 없다.

 

3. Register a User

사용자 등록을 하도록 한다. 사용자 등록시 사용하는 Password는 6글자가 넘어야 한다.

 

클라이언트 사이드

PCLSample.PCL -> AccountHelper.cs

            BaseUri = "http://localhost:22926";
            ServiceUri = "/api/Account/";
...

        public async Task<bool> RegistUser(string id, string pw)
        {
            var returnValue = false;

            //등록자 데이터를 JSON 형태로 만든다.

            var data = new PCLUser
                    {
                        UserName = id,
                        Password = pw,
                        ConfirmPassword = pw
                    };

            var json = JsonConvert.SerializeObject(data);

            using (var http = new HttpClient())
            {
                HttpContent hc = new StringContent(json);
                hc.Headers.ContentType = new MediaTypeHeaderValue("application/json");
                var uri = string.Format("{0}{1}{2}", BaseUri, ServiceUri, "Register");
                var resp = await http.PostAsync(uri, hc);
                if (resp != null && resp.IsSuccessStatusCode == true)
                {

                    //등록이 완료되면 토큰을 가지고 온다.
                    returnValue = await GetToken(id, pw);
                }

            }
            return returnValue;
        }

...

 

    class PCLUser
    {
        public string UserName { get; set; }
        public string Password { get; set; }
        public string ConfirmPassword { get; set; }
    }

 

서버 사이드 : 서버 소스는 처음에 만들어진 그대로 사용

AccountController -> Register

 

        // POST api/Account/Register
        [AllowAnonymous]
        [Route("Register")]
        public async Task<IHttpActionResult> Register(RegisterBindingModel model)
        {
            if (!ModelState.IsValid)
            {
                return BadRequest(ModelState);
            }

            IdentityUser user = new IdentityUser
            {
                UserName = model.UserName
            };

            IdentityResult result = await UserManager.CreateAsync(user, model.Password);
            IHttpActionResult errorResult = GetErrorResult(result);

            if (errorResult != null)
            {
                return errorResult;
            }

            return Ok();
        }

위의 메소드를 이용해서 사용자를 등록하면 ASP.Net 사용자 인증 db와 table들이 생성된다.

 

 

로컬DB에 2명의 사용자가 추가되어 있는 모습.

로컬db에 Account DB를 생성하려면, Web.config의 ConnectionString을 변경해 준다.

(자신의 DB설정에 맞는 값을 입력한다.)

  <connectionStrings>
    <add name="DefaultConnection" connectionString="Data Source=(local)\sqlexpress;Initial Catalog=PCLSample_Account;Integrated Security=SSPI" providerName="System.Data.SqlClient" />
  </connectionStrings>


 

4. Authenticate and Get a Bearer Token

인증하고 토큰 가져오기

 

클라이언트 사이드

AccountHelper -> GetToken

 

...

        public async Task<bool> GetToken(string id, string pw)
        {
            var returnValue = false;

            using (var http = new HttpClient())
            {
                HttpContent loginData = new FormUrlEncodedContent(
                        new List<KeyValuePair<string, string>>
                            {
                                new KeyValuePair<string, string>("grant_type", "password"),
                                new KeyValuePair<string, string>("username", id),
                                new KeyValuePair<string, string>("password", pw)
                            }
                    );
                try
                {
                    var apiLogin = string.Format("{0}{1}", BaseUri, "/Token");
                    var resp = await http.PostAsync(apiLogin, loginData);

                    if (resp != null && resp.IsSuccessStatusCode == true)
                    {

                        //인증 성공
                        var result = await resp.Content.ReadAsStringAsync();
                        if (string.IsNullOrEmpty(result) == false)
                        {

                            //토큰obj 저장
                            _clientToken = JsonConvert.DeserializeObject<PCLToken>(result);
                            returnValue = true;
                        }
                    }
                    else
                    {
                        if (resp.StatusCode == HttpStatusCode.BadRequest)
                        {

                           //인증 실패
                        }
                    }
                }
                catch (Exception ex)
                {
                    System.Diagnostics.Debug.WriteLine(ex.Message);
                }
            }

            return returnValue;
        }

...

 

    class PCLToken
    {
        public string access_token { get; set; }
        public string token_type { get; set; }
        public int expires_in { get; set; }
        public string userName { get; set; }
    }

 

서버 사이드 : 서버 소스는 그대로 사용

ApplicationOAuthProvider -> GrantResourceOwnerCredentials(), TokenEndpoint()

사용자 인증, 토큰 생성, 반환 과정

 

 

5. Using Token

클라이언트에서 반환된 토큰 사용하기

 

AccountHelper -> GetAuthHttp(), GetValue()

 

        public HttpClient GetAuthHttp()
        {
            HttpClient returnValue = new HttpClient();

            if (_clientToken == null) return returnValue;

            returnValue.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue(_clientToken.token_type, _clientToken.access_token);

            return returnValue;
        }

 

        public async Task<string> GetValue()
        {
            var returnValue = string.Empty;

            using (var http = GetAuthHttp())
            {
                var uri = string.Format("{0}{1}", BaseUri, "/api/values");
                var resp = await http.GetStringAsync(uri);
                if (string.IsNullOrEmpty(resp) == false)
                {
                    returnValue = resp;
                }
            }
            return returnValue;
        }

6. 실행 화면

 

 

실행은 PCLSample.WebAPISecurity 프로젝트와 PCLSample.W8 프로젝트만을 실행한다.

 

 

ID와 PW를 입력 후 Authentication버튼을 클릭하고, 정상적으로 처리가 되면 화면에 위와 같이 뜬다.

만약 인증을 할 수 없다는 메시지가 출력되면, Regist 버튼을 눌러서 등록을 시도한다. 등록시 비밀번호는 6자리 이상 입력해야 한다.

 

 

 

GetValue 버튼을 클릭하면 가지고 온 데이터를 화면에 출력한다.

 

7. 전체 소스

http://sdrv.ms/1bJHJPD

 

Posted by MVP kaki104

PCLSample 솔루션에 WebAPI를 이용해서 CRUD 하는 셈플을 완성했다. (옛날에는 무지 쉽게 했었는데..으흠;;)

이번 솔루션은 단순히 CRUD 오퍼레이션을 하는 것이 아니라, Repository pattern을 이용하도록 만들었다. 거의 실사용하는 수준으로 만들어져있는 부분들이 있으니 잘 참고 하면 간단한 서비스는 쉽게 만들 것이라 생각한다.

 

1. PCLSample.DAL

 

* PersonModelMap.cs

Id필드에 Identity속성 추가 - 속성이 추가 되기 위해서는 PCLSample 데이터베이스를 삭제 하고 다시 실행해야 적용된다.

    class PersonModelMap : System.Data.Entity.ModelConfiguration.EntityTypeConfiguration<PersonModel>
    {
        public PersonModelMap()
        {
            this.HasKey(f => f.Id);

            this.ToTable("People");

            this.Property(f => f.Id)
                .IsRequired()
                .HasDatabaseGeneratedOption(System.ComponentModel.DataAnnotations.Schema.DatabaseGeneratedOption.Identity)
                .HasColumnName("Id");
            this.Property(f => f.Name)
                .HasMaxLength(50)
                .HasColumnName("Name");
            this.Property(f => f.Age)
                .HasColumnName("Age");
            this.Property(f => f.Sex)
                .HasColumnName("Sex");
        }
    }

 

* GenericRepository.cs

Update를 호출할 때 key를 함께 넘겨주어 처리하도록 수정

 

        /// <summary>
        /// Entity를 받아서 수정
        /// </summary>
        /// <param name="entityToUpdate"></param>
        public virtual void Update(TEntity entityToUpdate, object key = null)
        {
            var entry = _context.Entry(entityToUpdate);
            if(key ==  null)
                key = this.GetPrimaryKey(entry);

            if (entry.State == EntityState.Detached)
            {
                var currentEntry = _dbSet.Find(key);
                if (currentEntry != null)
                {
                    var attachedEntry = _context.Entry(currentEntry);
                    attachedEntry.CurrentValues.SetValues(entityToUpdate);
                }
                else
                {
                    _dbSet.Attach(entityToUpdate);
                    entry.State = EntityState.Modified;
                }

            }
        }

 

구글에서 찾아서 추가를 했는데..실제로 동작은 않되는 것 같다.

        private object GetPrimaryKey(DbEntityEntry entry)
        {
            var myObject = entry.Entity;
            var property = myObject.GetType()
                             .GetProperties()
                             .FirstOrDefault(prop => Attribute.IsDefined(prop, typeof(KeyAttribute)));
            if (property == null)
                return null;
            return property.GetValue(myObject, null);
        }

* IGenericRepository.cs

    public interface IGenericRepository<TEntity>
     where TEntity : class
    {
        System.Linq.IQueryable<TEntity> AllGetting();
        void Delete(object id);
        void Delete(TEntity entityToDelete);
        void Detach(TEntity entityToDetach);
        void Dispose();
        System.Linq.IQueryable<TEntity> Getting(System.Linq.Expressions.Expression<Func<TEntity, bool>> filter = null, Func<System.Linq.IQueryable<TEntity>, System.Linq.IOrderedQueryable<TEntity>> orderBy = null, string includeProperties = "", int? page = null, int? pageSize = null);
        TEntity GettingByID(object id);
        void Insert(TEntity entity);
        void Update(TEntity entityToUpdate, object key = null);
    }

 

2. PCLSample.WebAPI

StartPage.html을 하나 추가해서, 시작페이지로 지정했다. 아무래도 뭔가 나오는 것이 좋다.

 

* PeopleController.cs

Repository pattern을 사용하는 컨트롤러는 자동생성이 않되기 때문에 수동으로 작업해 준다.

 

    public class PeopleController : ApiController
    {
        private IUnitOfWork _uow;

        public PeopleController()
        {
            _uow = GlobalVariables.Container.Resolve<IUnitOfWork>();
        }

        /// <summary>
        /// 조회 여러개
        /// </summary>
        /// <returns></returns>
        [Queryable]
        public IQueryable<PersonModel> GetPeople()
        {
            return _uow.PeopleRepo.AllGetting();
        }

        /// <summary>
        /// 조회 1개
        /// </summary>
        public PersonModel GetPeople(int Id)
        {
            return _uow.PeopleRepo.GettingByID(Id);
        }

        /// <summary>
        /// 수정
        /// </summary>
        /// <param name="id"></param>
        /// <param name="person"></param>
        /// <returns></returns>
        public HttpResponseMessage PutPeople(int id, PersonModel person)
        {
            if (ModelState.IsValid && id == person.Id)
            {
                _uow.PeopleRepo.Update(person, id);
                _uow.Save();

                return Request.CreateResponse(HttpStatusCode.OK);
            }
            else
            {
                return Request.CreateResponse(HttpStatusCode.BadRequest);
            }
        }

        /// <summary>
        /// 등록
        /// </summary>
        /// <param name="person"></param>
        /// <returns></returns>
        public HttpResponseMessage PostPeople(PersonModel person)
        {
            if (ModelState.IsValid)
            {
                _uow.PeopleRepo.Insert(person);
                _uow.Save();
                HttpResponseMessage response = Request.CreateResponse(HttpStatusCode.Created, person);
                response.Headers.Location = new Uri(Url.Link("DefaultApi", new { id = person.Id }));
                return response;
            }
            else
            {
                return Request.CreateResponse(HttpStatusCode.BadRequest);
            }
        }

        /// <summary>
        /// 삭제
        /// </summary>
        /// <param name="id"></param>
        /// <returns></returns>
        public HttpResponseMessage DeletePeople(int id)
        {
            var existItem = _uow.PeopleRepo.GettingByID(id);
            if (existItem == null)
            {
                return Request.CreateResponse(HttpStatusCode.NotFound);
            }

            _uow.PeopleRepo.Delete(existItem);
            _uow.Save();

            return Request.CreateResponse(HttpStatusCode.OK, existItem);
        }
    }

 

 

3. PCLSample.PCL

 

* WebAPIBase.cs

 

    public abstract class WebAPIBase<T> : BindableBase
    {
        /// <summary>
        /// 베이스Uri
        /// </summary>
        public string BaseUri { get; set; }

        /// <summary>
        /// 서비스 Uri
        /// </summary>
        public string ServiceUri { get; set; }

        /// <summary>
        /// 결과코드
        /// </summary>
        public string ResultCode { get; set; }

        /// <summary>
        /// 결과메시지
        /// </summary>
        public string ResultMsg { get; set; }

        /// <summary>
        /// 반환갯수
        /// </summary>
        protected int NumOfRows { get; set; }

        private int pageNo;
        /// <summary>
        /// 페이지 번호
        /// </summary>
        public int PageNo
        {
            get { return pageNo; }
            set
            {
                pageNo = value;
                OnPropertyChanged();
            }
        }

        private int totalCount;
        /// <summary>
        /// 전체 카운트
        /// </summary>
        public int TotalCount
        {
            get { return totalCount; }
            set
            {
                totalCount = value;
                OnPropertyChanged();
            }
        }

        /// <summary>
        /// 토탈 페이지 = totalCount / NumOfRows + 1
        /// </summary>
        public int TotalPage { get; set; }

        private bool isBusy;
        /// <summary>
        /// 작업중 여부
        /// </summary>
        public bool IsBusy
        {
            get { return isBusy; }
            set { isBusy = value; OnPropertyChanged(); }
        }

        /// <summary>
        /// 생성자
        /// </summary>
        public WebAPIBase()
        {
            BaseUri = "";
            ServiceUri = "";
            NumOfRows = 30;
            PageNo = 1;

            this.PropertyChanged +=
                (s, e) =>
                {
                    switch (e.PropertyName)
                    {
                        case "NumOfRows":
                        case "TotalCount":
                            if (TotalCount > 0 && NumOfRows > 0)
                            {
                                TotalPage = TotalCount / NumOfRows;
                                if ((TotalCount % NumOfRows) > 0)
                                    TotalPage++;
                            }
                            else
                            {
                                TotalPage = 0;
                            }
                            break;
                    }
                };
        }

        /// <summary>
        /// 조회
        /// </summary>
        public async Task<string> GetData(string requestUri)
        {
            string result = string.Empty;
            IsBusy = true;

            using (HttpClient hc = new HttpClient())
            {
                try
                {
                    //타임아웃30초
                    hc.Timeout = new TimeSpan(0, 0, 30);
                    var uri = string.Format("{0}{1}{2}", BaseUri, ServiceUri, requestUri);
                    result = await hc.GetStringAsync(uri);
                }
                catch (Exception ex)
                {
                    System.Diagnostics.Debug.WriteLine(ex.Message);
                    IsBusy = false;
                }
            }
            IsBusy = false;
            return result;
        }

 

        /// <summary>
        /// 조회 목록
        /// </summary>
        public async Task<IList<T>> GetDatas(string requestUri)
        {
            IList<T> returnValue = new List<T>();
            IsBusy = true;

            using (HttpClient hc = new HttpClient())
            {
                try
                {
                    hc.Timeout = new TimeSpan(0, 0, 30);
                    var uri = string.Format("{0}{1}{2}", BaseUri, ServiceUri, requestUri);
                    var result = await hc.GetAsync(uri);
                    if (result.IsSuccessStatusCode == true)
                    {
                        var data = await result.Content.ReadAsStringAsync();
                        if (data != null)
                        {
                            returnValue = JsonConvert.DeserializeObject<List<T>>(data);
                        }
                    }
                }
                catch (Exception ex)
                {
                    System.Diagnostics.Debug.WriteLine(ex.Message);
                    IsBusy = false;
                }
            }
            IsBusy = false;
            return returnValue;
        }

 

        /// <summary>
        /// 등록
        /// </summary>
        /// <param name="postItem"></param>
        /// <returns></returns>
        public virtual async Task<T> PostAsync(T postItem)
        {
            T returnValue = default(T);
            if (postItem != null)
            {
                var json = JsonConvert.SerializeObject(postItem);
                if (json != null && json.Length > 0)
                {
                    using (var hc = new HttpClient())
                    {
                        var sc = new StringContent(json, Encoding.UTF8, "application/json");
                        try
                        {
                            var uri = string.Format("{0}{1}", BaseUri, ServiceUri);
                            var resp = await hc.PostAsync(uri, sc);
                            if (resp.IsSuccessStatusCode == true)
                            {
                                var content = await resp.Content.ReadAsStringAsync();
                                returnValue = JsonConvert.DeserializeObject<T>(content);
                            }
                        }
                        catch (Exception ex)
                        {
#if DEBUG
                            System.Diagnostics.Debug.WriteLine(ex.Message);
                            throw;
#endif
                        }
                    }
                }
            }
            return returnValue;
        }

 

        /// <summary>
        /// 수정
        /// </summary>
        /// <param name="putItem"></param>
        /// <returns></returns>
        public virtual async Task<bool> PutAsync(T putItem, object key)
        {
            bool returnValue = false;

            var json = JsonConvert.SerializeObject(putItem);
            if (json != null && json.Length > 0)
            {
                using (var hc = new HttpClient())
                {
                    var sc = new StringContent(json, Encoding.UTF8, "application/json");
                    var uri = string.Format("{0}{1}{2}", BaseUri, ServiceUri, key.ToString());
                    try
                    {
                        var resp = await hc.PutAsync(uri, sc);
                        if (resp.IsSuccessStatusCode == true)
                        {
                            returnValue = true;
                        }
                    }
                    catch (Exception ex)
                    {
#if DEBUG
                        System.Diagnostics.Debug.WriteLine(ex.Message);
                        throw;
#endif
                    }
                }
            }
            return returnValue;

        }

 

        /// <summary>
        /// 삭제
        /// </summary>
        /// <param name="deleteItem"></param>
        /// <returns></returns>
        public virtual async Task<bool> DeleteAsync(T deleteItem, object key)
        {
            bool returnValue = false;

            using (var hc = new HttpClient())
            {
                var query = string.Format("{0}{1}{2}", BaseUri, ServiceUri, key.ToString());
                try
                {
                    var resp = await hc.DeleteAsync(query);
                    if (resp.IsSuccessStatusCode)
                    {
                        returnValue = true;
                    }
                }
                catch (Exception ex)
                {
#if DEBUG
                    System.Diagnostics.Debug.WriteLine(ex.Message);
                    throw;
#endif
                }
            }
            return returnValue;
        }

    }

 

* PersonHelper.cs

 

    public class PersonHelper : WebAPIBase<PersonModel>
    {
        /// <summary>
        /// 인스턴스
        /// </summary>
        private readonly static PersonHelper instance = new PersonHelper();

        /// <summary>
        /// 인스턴스 프로퍼티
        /// </summary>
        public static PersonHelper Instance
        {
            get
            {
                return instance;
            }
        }
       
        /// <summary>
        /// 생성자
        /// </summary>
        public PersonHelper()
            : base()
        {
            BaseUri = "http://localhost:59409";
            ServiceUri = "/api/People/";

        }

    }

 

* MainViewModel.cs

 

        private DelegateCommand _getListCommand;
        public DelegateCommand GetListCommand
        {
            get
            {
                return _getListCommand = _getListCommand ?? new DelegateCommand(
                    async para =>
                    {
                        var result = await PersonHelper.Instance.GetDatas("");
                        People = new ObservableCollection<PersonModel>(result);
                    });
            }
        }
        private DelegateCommand _newCommand;
        public DelegateCommand NewCommand
        {
            get
            {
                return _newCommand = _newCommand ?? new DelegateCommand(
                    para =>
                    {
                        Person = new PersonModel();
                        People.Add(Person);
                    });
            }
        }

        private DelegateCommand _updateCommand;
        public DelegateCommand UpdateCommand
        {
            get
            {
                return _updateCommand = _updateCommand ?? new DelegateCommand(
                    async para =>
                    {
                        if (Person == null) return;

                        if (Person.Id == 0)
                        {
                            var newPerson = await PersonHelper.Instance.PostAsync(Person);
                            Person.Id = newPerson.Id;
                        }
                        else
                        {
                            var update = await PersonHelper.Instance.PutAsync(Person, Person.Id);
                        }
                    });
            }
        }

        private DelegateCommand _deleteCommand;
        public DelegateCommand DeleteCommand
        {
            get
            {
                return _deleteCommand = _deleteCommand ?? new DelegateCommand(
                    async para =>
                    {
                        if (Person != null)
                        {
                            var result = await PersonHelper.Instance.DeleteAsync(Person, Person.Id);
                            if (result == true)
                            {
                                People.Remove(Person);
                                Person = null;
                            }
                        }
                    });
            }
        }

4. PCLSample.WPF

 

2군데 수정

* MainWindow.xaml, MainWindow.xaml.cs

 

실버라이트 프로젝트와 Store app 프로젝트는 직접 수정하도록 한다.

 

5. 솔루션 프로퍼티에서 시작 프로젝트를 2개로 지정한다.

 

F5로 실행하면 아래와 같이 2개의 창이 뜬다.

 

 

 

6. 오퍼레이션

 

 

 

 

Id 5번을 선택 한 후

Name : kaki110, Age : 30으로 수정 후 Update

 

 

 

New 클릭 -> Name : kaki120, Age : 33, Sex : uncheck -> Update 클릭

 

 

Id 3번 선택 후 Delete를 클릭

 

 

 

전체 소스

http://sdrv.ms/1cBaryF

 

Posted by MVP kaki104

Visual Studio 2013에서 ApiController를 이용해서 OData Query를 사용하는 방법에 대해 포스팅한다.

 

참고 포스트

Supporting OData Query Options

http://www.asp.net/web-api/overview/odata-support-in-aspnet-web-api/supporting-odata-query-options

 

이 포스트의 핵심은 기존 ApiController를 이용해서도 OData쿼리를 할 수 있다는 내용이다.(사실 이것이 정상이지..처음에는 되었다가 나중에 않되었다가 다시 풀어준..그런 기가막힌 사연을 가지고 있다)

 

PCLSample.WebAPI 프로젝트를 추가했다.

기본 프로젝트는 이전 포스트 처럼 Empty템플릿과 WebAPI만을 선택해서 만든 후 추가적인 Nuget packages를 추가한다.

 

* Nuget packages : 필수 항목만 나열한다.

EntityFramework : Database 연동을 하기 위한 프레임웍

Microsoft ASP.NET Web API OData : OData Query를 사용하기 위한 필수 항목

Portable IoC : IoC를 사용하기 위한 패키지

그외 나머지 내용들은 위의 내용 설치시 함께 설치가 되거나, PCL에서 사용하는 패키지들이라 함께 설치한 것이다.

 

위의 패키지들을 설치한 후

 

WebApiConfig.cs

 

        public static void Register(HttpConfiguration config)
        {
            // Web API configuration and services

            // Web API routes
            config.MapHttpAttributeRoutes();

            config.Routes.MapHttpRoute(
                name: "DefaultApi",
                routeTemplate: "api/{controller}/{id}",
                defaults: new { id = RouteParameter.Optional }
            );

            config.EnableQuerySupport();        //OData Query 사용 가능 옵션 활성화
        }

 

PeopleController.cs

 

    public class PeopleController : ApiController
    {
        private IUnitOfWork _uow;

        public PeopleController()
        {
            _uow = GlobalVariables.Container.Resolve<IUnitOfWork>();
        }

        /// <summary>
        /// 조회
        /// </summary>
        /// <returns></returns>
        [Queryable]
        public IQueryable<PersonModel> Get()
        {
            return _uow.PeopleRepo.AllGetting();
        }
    }

GlobalVariables.cs

전역에서 사용할 스택틱 클래스 선언

    public static class GlobalVariables
    {
        private static IPortableIoC _container;
        /// <summary>
        /// PortableIoC Conatiner
        /// </summary>
        public static IPortableIoC Container
        {
            get { return _container = _container ?? new PortableIoc(); }
            private set { _container = value; }
        }
    }

 

Global.asax

ApiController에서 사용할 인터페이스를 등록하고, 데이터 베이스 초기화할때 실행할 내용을 지정한다.

 

        protected void Application_Start()
        {
            GlobalConfiguration.Configure(WebApiConfig.Register);

            //Regiser user interface
            GlobalVariables.Container.Register<IUnitOfWork>(ioc => new UnitOfWork());

            //db init
            System.Data.Entity.Database.SetInitializer(new PCLSampleContextInitializer());
        }

 

Web.config

Database연결 문자열은 자신의 환경에 맞게 수정한다.

...

  <connectionStrings>
    <add name="PCLSampleContext" connectionString="Data Source=(local)\sqlexpress;Initial Catalog=PCLSample;Integrated Security=SSPI" providerName="System.Data.SqlClient" />
  </connectionStrings>
...

 

F5로 실행

웹 페이지가 없기 때문에 별 내용은 나오지 않는다.

 

주소창에 http://localhost:59409/api/People 입력

결과 (서비스가 시작되면서 db가 생성되었을때 Seed가 입력되어 있음)

[{"Id":1,"Age":20,"Name":"kaki104","Sex":true}

,{"Id":2,"Age":21,"Name":"kaki105","Sex":false}

,{"Id":3,"Age":22,"Name":"kaki106","Sex":true}

,{"Id":4,"Age":23,"Name":"kaki107","Sex":false}

,{"Id":5,"Age":24,"Name":"kaki108","Sex":true}]

 

http://localhost:59409/api/People?$filter=Name eq 'kaki105'

결과

[{"Id":2,"Age":21,"Name":"kaki105","Sex":false}]

 

http://localhost:59409/api/People?$skip=2&$top=2

결과

[{"Id":3,"Age":22,"Name":"kaki106","Sex":true},{"Id":4,"Age":23,"Name":"kaki107","Sex":false}]

 

쿼리 옵션이나 추가 기능은 참고 포스트를 보면 나와있다

 

ODataController인 경우에는 기본적으로 OData Query를 사용할 수 있다.

 

전체 소스

http://sdrv.ms/Ii8hLb

 

 

Posted by MVP kaki104

지난번에도 이와 관련한 포스트를 올렸는데..Visual Studio 2013이 나온 후 이전과는 완전 다른 방법으로 만들도록 되어 있어서, 다시 포스팅을 한다. 이제는 다시 뒤집지 않았으면 하는 바램이..쿨럭;;

 

참고 포스트

Creating an OData Endpoint in ASP.NET Web API

http://www.asp.net/web-api/overview/odata-support-in-aspnet-web-api/creating-an-odata-endpoint

 

* 이 포스트는 참고 포스트를 거의 그대로 따라하는 내용임

 

요구사항

Visual Studio 2013과 ASP.NET and Web Tools 2012.2 Update 가 설치 되어 있어야지만 사용 가능

 

프로젝트 만들기

File -> New -> Project -> New Project 창에서 Web을 선택하고 ASP.NET Web Application을 선택

 

기타 프로젝트 이름은 그대로 동일하게 사용했고 위치는 본인 컴퓨터에 위치를  

 

 

 

Empty템플릿을 선택하고 Web API에 체크를 한 후 OK를 눌러서 프로젝트를 생성한다.

 

 

모델 추가

솔루션 탐색기에서 Models폴더를 선택 한 후 마우스 오른쪽 클릭 -> Add -> Class 선택

클래스명에 Product를 입력 후 Add 클릭

 

기본 Product.cs 모델이 추가되는데

public class Product
{
   
public int ID { get; set; }
   
public string Name { get; set; }
   
public decimal Price { get; set; }
   
public string Category { get; set; }
}

위의 내용을 복사해서 붙여 넣는다.

 

Code-First에서 ID, Id의 이름을 가지는 프로퍼티는 테이블로 만들어 질때 키 값으로 사용된다.

복사해서 붙여 넣은 후 꼭 F6을 눌러서 빌드를 해준 후 다음으로 넘어간다.(빌드를 하지 않을 경우 스카폴딩이 않되서 오류가 발생한다.)

 

OData 컨트롤러 추가

솔루션 탐색기에서 Controller 폴더를 선택 한 후 마우스 오른쪽 클릭 -> Add -> Controller 선택

 

상당히 마음에 드는 화면인데, 이전에 비해서 많은 발전을 한 것 같은 느낌이 든다. Web API 2 OData Controller with actions, using Entity Framework를 선택 한다.

 

컨트롤러 이름은 위와 같이 수정하고, Use Async controller actions에 체크를 해서 비동기 형태로 만들어 지게 한다. 모델은 드롭다운 박스에서 하나를 선택하면 되고, Data context는 New data context 버튼을 눌러 만들면 된다.

 

스카폴딩이 완료되면, ProductsController.cs, ProductServiceContext.cs파일이 추가된 것을 확인 할 수 있다.

 

EDM과 Route 추가

솔루션 탐색기에서 App_Start 폴더를 확장하면 WebApiConfig.cs파일이 있는데 이 부분을 수정해서 WebAPI에서 모델을 사용할 수 있도록 한다.

 

using ProductService.Models;
using System.Web.Http;
using System.Web.Http.OData.Builder;

namespace ProductService
{
   
public static class WebApiConfig
   
{
       
public static void Register(HttpConfiguration config)
       
{

               //복사해서 붙여 넣으면된다.
           
ODataConventionModelBuilder builder = new ODataConventionModelBuilder();
            builder
.EntitySet<Product>("Products");
            config
.Routes.MapODataRoute("odata", "odata", builder.GetEdmModel());
       
}
   
}
}

 

각 코드의 내용이 궁금하면 원본 포스트에 설명이 있으니 참고하면 된다. 다음으로 넘어가기 전에 F6을 눌러 빌드를 한번씩 해주는 것이 좋다.

 

데이터베이스 시드(초기값)

이번에 새로운 방법으로 만들면서 좋게 변했다고 생각하는 부분이 이제 시작되는데, 데이터베이스를 만들고 초기값을 넣어야 하는 경우에 일일이 코딩으로 만들어서 처리를 했는데, 이제 이 부분까지 어느정도 자동으로 처리가 가능하다.

 

솔루션 탐색기 -> 프로젝트 선택 -> Tool -> Library Package Manager -> Package Manager Console을 선택하면 Console창이 하나 나타 난다. 일반적으로는 이곳에 Nuget package를 설치하는 명령어를 입력하게 되었었는데

 

Enable-Migrations

이란 단어를 입력하고 엔터를 치면, 솔루션 탐색기에 Migrations이라는 폴더가 생성이 되고, Configurations.cs파일이 추가 되어있는 것을 확인 할 수 있다.

 

Configuration.Seed 메소드를 아래와 같이 수정한다. 물론 네임스페이스는 추가해 주어야한다.

 

protected override void Seed(ProductService.Models.ProductServiceContext context)
{
   
// New code
    context
.Products.AddOrUpdate(new Product[] {
       
new Product() { ID = 1, Name = "Hat", Price = 15, Category = "Apparel" },
       
new Product() { ID = 2, Name = "Socks", Price = 5, Category = "Apparel" },
       
new Product() { ID = 3, Name = "Scarf", Price = 12, Category = "Apparel" },
       
new Product() { ID = 4, Name = "Yo-yo", Price = 4.95M, Category = "Toys" },
       
new Product() { ID = 5, Name = "Puzzle", Price = 8, Category = "Toys" },
   
});
}

 

그리고, Console창에 아래 두가지 명령을 더 입력한다.

 

Add-Migration Initial
Update-Database

 

마이그레이션 폴더에 초기화 클래스가 추가되고, Database 연결 문자열도 Web.config에 추가된다.

 

여기까지 작업 후 F5를 눌러 실행한다.

 

뭔가 잘못된 것은 아니다, 단지 이 서비스는 웹 페이지가 없기 때문에 위와 같이 나오는 것 뿐이니 넘어가자

 

http://localhost:52742/odata

주소 창에 위와 같이 입력하면 아래와 같은 결과가 반환된다.

 

<?xml version="1.0" encoding="utf-8"?>
<service xml:base="http://localhost:52742/odata" xmlns="http://www.w3.org/2007/app" xmlns:atom="http://www.w3.org/2005/Atom">
  <workspace>
    <atom:title type="text">Default</atom:title>
    <collection href="Products">
      <atom:title type="text">Products</atom:title>
    </collection>
  </workspace>
</service>

 

http://localhost:52742/odata/Products

주소 창에 위와 같이 입력하면 아래와 같은 결과가 반환된다.

{
  "odata.metadata":"http://localhost:52742/odata/$metadata#Products","value":[
    {
      "ID":1,"Name":"Hat","Price":"15.00","Category":"Apparel"
    },{
      "ID":2,"Name":"Socks","Price":"5.00","Category":"Apparel"
    },{
      "ID":3,"Name":"Scarf","Price":"12.00","Category":"Apparel"
    },{
      "ID":4,"Name":"Yo-yo","Price":"4.95","Category":"Toys"
    },{
      "ID":5,"Name":"Puzzle","Price":"8.00","Category":"Toys"
    }
  ]
}

 

여기까지 하면 기본적인 서비스 생성이 완료된 것이다. 이전 버전에 비해 좀더 고급스러워 졌다는 느낌이 든다.

 

Posted by MVP kaki104

마지막으로 각 클라이언트에서 DataService를 사용하는 방법에 대해 정리하도록 하자.

 

이전 포스트

OData Endpoint with ASP.NET Web API Part2 - Web

OData Endpoint with ASP.NET Web API Part1 - DAL

 

1. 기본 참고 포스트

Create a C# Client for the OData Endpoint

http://www.asp.net/web-api/overview/odata-support-in-aspnet-web-api/getting-started-with-odata-in-web-api/create-an-odata-client

 

2. WCF Data Services Tool Install

OData Endpoint DataService를 사용하기 위해서는 별도의 Tool을 설치해야한다.

WCF Data Services 5.6.0 RTM Tools Installer

http://www.microsoft.com/en-us/download/details.aspx?id=39373

설명에는 모든 플랫폼에서 사용가능하다고 나와있다. 이전 버전에서는 몇개의 인스톨을 추가로 해줘야 가능했는데..하나로 통합이 되지 않았을까 추측해 본다.

만약, Windows 8 store app에서 Service References에 추가가 되지 않는다면 Windows 8용 tool을 찾아서 설치해주어야 한다.

* 하나로 통합이 되어 있는 것 확인

대신 PCLSample.W8 프로젝트의 packages.config 파일을 열어서 아래 두 부분을 삭제 한다. Nuget package에서는 아래 내용을 찾을 수 없다.

  <package id="Microsoft.Data.OData.WindowsStore" version="5.0.0" targetFramework="win" />
  <package id="Microsoft.Data.Services.Client.WindowsStore" version="5.0.0" targetFramework="win" />

 

3. 프로젝트 실행

Ctrl + F5를 눌러서 Web프로젝트를 실행한다. 서비스가 올라가야 Service Reference를 추가할 수 있다.

 

 

Web 프로젝트가 실행되면 실버라이트 페이지가 자동으로 오픈된다. 주소창에 아래와 같이 입력해 본다.

http://localhost:2852/odata/ : odata를 이용해서 접근할 수 있는 곳이 People이라는 것을 알 수 있다.

odata를 저장하겠냐고 물어보고, 이 데이터를 저장 후 메모장으로 열어 보면 아래와 같은 내용이 표시된다.

 

<?xml version="1.0" encoding="utf-8"?>
<service xml:base="http://localhost:2852/odata/" xmlns="http://www.w3.org/2007/app" xmlns:atom="http://www.w3.org/2005/Atom">
  <workspace>
    <atom:title type="text">Default</atom:title>
    <collection href="People">
      <atom:title type="text">People</atom:title>
    </collection>
  </workspace>
</service>

 

 

http://localhost:2852/odata/$metadata : 전체 스키마 구조를 알 수 있다.

 

<?xml version="1.0" encoding="utf-8"?>
<edmx:Edmx Version="1.0" xmlns:edmx="http://schemas.microsoft.com/ado/2007/06/edmx">
  <edmx:DataServices m:DataServiceVersion="3.0" m:MaxDataServiceVersion="3.0" xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata">
    <Schema Namespace="PCLSample.PCL.Models" xmlns="http://schemas.microsoft.com/ado/2009/11/edm">
      <EntityType Name="PersonModel">
        <Key>
          <PropertyRef Name="Id" />
        </Key>
        <Property Name="Id" Type="Edm.Int32" Nullable="false" />
        <Property Name="Age" Type="Edm.Int32" Nullable="false" />
        <Property Name="Name" Type="Edm.String" />
        <Property Name="Sex" Type="Edm.Boolean" Nullable="false" />
      </EntityType>
    </Schema>
    <Schema Namespace="Default" xmlns="http://schemas.microsoft.com/ado/2009/11/edm">
      <EntityContainer Name="Container" m:IsDefaultEntityContainer="true">
        <EntitySet Name="People" EntityType="PCLSample.PCL.Models.PersonModel" />
      </EntityContainer>
    </Schema>
  </edmx:DataServices>
</edmx:Edmx>

 

4. PCLSample.SL5

Solution Explorer에서

PCLSample.SL5 프로젝트를 선택 -> 마우스 오른쪽 클릭 -> Add Service References

 

 

Address에 위와 같이 입력하고 Go를 누르면 Services 목록이 표시되며, 이 서비스를 PCLSampleService라는 이름을 프로젝트에 추가한다. 이 과정은 각 프로젝트마다 동일하다.

 

5. MainPage.xaml.cs

원래는 뷰 모델에서 처리해야하는데 현재 버전에서는 cs에서 작업하고 있다.

방금 추가한 서비스를 이용해서 데이터를 조회하고, 결과를 debug창에 출력한다.

 

    public partial class MainPage : UserControl
    {
        public IMainViewModel ViewModel
        {
            get { return DataContext as IMainViewModel; }
            set { DataContext = value; }
        }

        public MainPage()
        {
            InitializeComponent();

            ViewModel = App.GetInstance<IMainViewModel>();

            this.Loaded += MainPage_Loaded;
            this.Unloaded += MainPage_Unloaded;
        }

        void MainPage_Unloaded(object sender, RoutedEventArgs e)
        {
            this.Loaded -= MainPage_Loaded;
            this.Unloaded -= MainPage_Unloaded;
        }

 

        async void MainPage_Loaded(object sender, RoutedEventArgs e)
        {
            var result = await GetPeopleAsync();
            if (result == null) return;
            foreach (var item in result)
            {
                System.Diagnostics.Debug.WriteLine(string.Format("Silverlight5 Id:{0} Name:{1} Age:{2} Sex:{3}", item.Id, item.Name, item.Age, item.Sex));
            }
        }

 

        private async Task<IList<PersonModel>> GetPeopleAsync()
        {
            IList<PersonModel> returnValue = null;

            var container = new PCLSampleService.Container(new Uri("http://localhost:2852/odata/"));
            var dsq = container.People;
            TaskFactory<IList<PersonModel>> tf = new TaskFactory<IList<PersonModel>>();
            await TaskEx.Delay(2000);        //서비스가 실행되기 전에 호출하면 오류가 발생해서 지연 시킴
            returnValue = await tf.FromAsync(dsq.BeginExecute(null, null), iar =>
            {
                var result = dsq.EndExecute(iar);
                if (result != null)
                {
                    return result.ToList();
                }
                else
                {
                    return null;
                }
            });
            return returnValue;
        }

    }

 

6. Output

 

Silverlight5 Id:1 Name:kaki104 Age:20 Sex:True
Silverlight5 Id:2 Name:kaki105 Age:21 Sex:False
Silverlight5 Id:3 Name:kaki106 Age:22 Sex:True
Silverlight5 Id:4 Name:kaki107 Age:23 Sex:False
Silverlight5 Id:5 Name:kaki108 Age:24 Sex:True

 

7. Database

 

 

database에 Seed로 입력한 값이 들어가 있는 것을 알 수 있다.

 

8. 마무리

위와 같은 동일한 과정으로 Service References를 추가하고 사용하는 것을 Windows 8 store app, WPF에서도 동일하게 작업 했다.

자세한 사항은 아래 소스를 참고 하기 바란다.

 

9. 소스

SkyDrive link

http://sdrv.ms/18gTb1U

 

nuget package가 빠진 소스이며, 솔루션에서 마우스 오른쪽 클릭해서

Manage NuGet Packages for Solution..을 선택 하신 후 Restore를 선택하면 자동 인스톨 됨

nuget package가 포함된 소스가 필요하다면 리플로 신청 요망(메일주소와 함께)

 

Posted by MVP kaki104

이전 포스트에서 DAL 프로젝트 설정에 대해서 알아 보았다. 이제 Web 프로젝트에 WebAPI를 OData Endpoint로 구현하는 방법에 대해서 알아보자

 

1. PCLSample.Web

Silverlight와 WCF DataService를 호스팅하는 프로젝트

.Net Framework 4.5, MVC4, WebAPI 

 

Nuget packages

너무 많아서 모두 적을 수 없고, 핵심되는 몇개만 기술

PortableIoC

Microsoft.AspNet.WebApi.OData

Microsoft.Data.OData

System.Spatial

 

Create a Read-Only OData Endpoint with ASP.NET Web API

http://www.asp.net/web-api/overview/odata-support-in-aspnet-web-api/getting-started-with-odata-in-web-api/create-a-read-only-odata-endpoint

 

위의 포스트에 있는 내용을 기준으로 생각하면되고, Nuget package 중 PortableIoC는 Nuget에서 검색해서 설치하고, 나머지는 Nuget Console 모드에서 Install-Package Microsoft.AspNet.WebApi.OData를 입력해서 설치하면된다.

 

1-1. GlobalVariables

웹 전체에서 사용할 클래스로 IoC Container를 가지고 있다.

 

    /// <summary>
    /// 전체 공용
    /// </summary>
    public static class GlobalVariables
    {
        private static IPortableIoC _container;
        /// <summary>
        /// PortableIoC Conatiner
        /// </summary>
        public static IPortableIoC Container
        {
            get { return _container = _container ?? new PortableIoc(); }
            private set { _container = value; }
        }
    }

 

 

1-2. Glabal.asax.cs

웹이 시작하면서 컨테이너에 인터페이스들을 등록한다.

 

    public class WebApiApplication : System.Web.HttpApplication
    {
        protected void Application_Start()
        {
            AreaRegistration.RegisterAllAreas();

            WebApiConfig.Register(GlobalConfiguration.Configuration);
            FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
            RouteConfig.RegisterRoutes(RouteTable.Routes);
            BundleConfig.RegisterBundles(BundleTable.Bundles);

 

            //Regiser user interface
            GlobalVariables.Container.Register<IUnitOfWork>(ioc => new UnitOfWork());

            //db init
            System.Data.Entity.Database.SetInitializer(new PCLSampleContextInitializer());

        }
    }

 

1-3. WebApiConfig

WebApi설정 클래스로 기존에 있던 내용을 모두 주석처리하고 아래 내용으로 변경한다.

 

    public static class WebApiConfig
    {
        public static void Register(HttpConfiguration config)
        {
            ODataModelBuilder modelBuilder = new ODataConventionModelBuilder();
            modelBuilder.EntitySet<PersonModel>("People");

 

            Microsoft.Data.Edm.IEdmModel model = modelBuilder.GetEdmModel();
            config.Routes.MapODataRoute("ODataRoute", "odata", model);

 

            config.EnableQuerySupport();
        }
    }

 

1-4. PeopleController

WebAPI OData Endpoint controller로 구성방법에 대해서는

 

Supporting OData CRUD Operations in ASP.NET Web API

http://www.asp.net/web-api/overview/odata-support-in-aspnet-web-api/supporting-odata-crud-operations

포스트를 참고한다.

 

실제 CRUD를 모두 테스트를 해보지 않아서 차후에 수정이 될 것이라 생각한다.

 

    public class PeopleController : EntitySetController<PersonModel, int>
    {
        private IUnitOfWork _uow;

        public PeopleController()
        {
            _uow = GlobalVariables.Container.Resolve<IUnitOfWork>();
        }

        /// <summary>
        /// 조회 여러개
        /// </summary>
        /// <returns></returns>
        public override IQueryable<PersonModel> Get()
        {
            return _uow.PeopleRepo.AllGetting();
        }

        /// <summary>
        /// 조회 1개
        /// </summary>
        /// <param name="key"></param>
        /// <returns></returns>
        protected override PersonModel GetEntityByKey(int key)
        {
            return _uow.PeopleRepo.GettingByID(key);
        }

        /// <summary>
        /// Dispose
        /// </summary>
        /// <param name="disposing"></param>
        protected override void Dispose(bool disposing)
        {
            //_unitOfWork.Dispose();
            base.Dispose(disposing);
        }
        /// <summary>
        /// POST
        /// </summary>
        /// <param name="entity"></param>
        /// <returns></returns>
        protected override PersonModel CreateEntity(PersonModel entity)
        {
            _uow.PeopleRepo.Insert(entity);
            _uow.Save();
            return entity;
        }
        /// <summary>
        /// POST
        /// </summary>
        /// <param name="entity"></param>
        /// <returns></returns>
        protected override int GetKey(PersonModel entity)
        {
            return entity.Id;
        }

        /// <summary>
        /// PUT
        /// </summary>
        /// <param name="key"></param>
        /// <param name="update"></param>
        /// <returns></returns>
        protected override PersonModel UpdateEntity(int key, PersonModel update)
        {
            if (_uow.PeopleRepo.GettingByID(key) == null)
            {
                throw new HttpResponseException(HttpStatusCode.NotFound);
            }

            _uow.PeopleRepo.Update(update);
            _uow.Save();
            return update;
        }

        /// <summary>
        /// PUT ?
        /// </summary>
        /// <param name="key"></param>
        /// <param name="patch"></param>
        /// <returns></returns>
        protected override PersonModel PatchEntity(int key, Delta<PersonModel> patch)
        {
            PersonModel user = _uow.PeopleRepo.GettingByID(key);
            if (user == null)
            {
                throw new HttpResponseException(HttpStatusCode.NotFound);
            }
            patch.Patch(user);
            _uow.Save();
            return user;
        }

        /// <summary>
        /// DELETE
        /// </summary>
        /// <param name="key"></param>
        public override void Delete(int key)
        {
            PersonModel user = _uow.PeopleRepo.GettingByID(key);
            if (user == null)
            {
                throw new HttpResponseException(HttpStatusCode.NotFound);
            }
            _uow.PeopleRepo.Delete(key);
            _uow.Save();
        }

    }

 

1-5. Web.config

ConnectionString을 추가해 준다.

 

  <connectionStrings>
    <add name="DefaultConnection" providerName="System.Data.SqlClient" connectionString="Data Source=(LocalDb)\v11.0;Initial Catalog=aspnet-PCLSample.Web-20130627171150;Integrated Security=SSPI;AttachDBFilename=|DataDirectory|\aspnet-PCLSample.Web-20130627171150.mdf" />
    <add name="PCLSampleContext" connectionString="Data Source=(local)\sqlexpress;Initial Catalog=PCLSample;Integrated Security=SSPI" providerName="System.Data.SqlClient" />
  </connectionStrings>

 

2. 위와 같이 하면 서버 구성은 완료가 된다.

다음 포스트는 클라이언트에서 DataServie를 추가하는 방법과 사용법에 대해 다루도록 하겠다.

 

Posted by MVP kaki104

MVC4에서 WebAPI를 이용해서 CRUD가 가능하다는 부분은 이전 포스트에서 다루었다. 이번에는 WebAPI를 OData Endpoint로 변경해서 WCF DataService로 사용하는 방법에 대해서 살펴 보려고 한다.

 

1. 기본 참고 포스트

Creating an OData Endpoint in ASP.NET Web API

http://www.asp.net/web-api/overview/odata-support-in-aspnet-web-api/creating-an-odata-endpoint

 

Requirements

This tutorial uses Visual Studio 2013.

Web API OData support was added in ASP.NET and Web Tools 2012.2 Update. However, this tutorial uses scaffolding that was added in Visual Studio 2013.

 

2. WebAPI vs WCF DataService

A New Option for Creating OData: Web API

http://msdn.microsoft.com/en-us/magazine/dn201742.aspx

데이터 서비스는 국내에서는 잘 사용되고 있지 않은 서비스 중에 하나라고 생각한다. 그런데도 MS에서는 WebAPI를 WCF DataService를 이용해서 OData Endpoint를 관리하도록 만들어 놓았다.

장점과 단점이 있지만 판단은 사용자가 하는 것이니 사용해 보고 판단하면 될 것 같다.

 

3. 셈플 프로젝트 구성

이전에 PCLSample 프로젝트를 확장했다.

PCLSample.DAL : Data Access Layer

PCLSample.PCL : Portable Class Library

PCLSample.SL5 : Silverlight 5

PCLSample.W8 : Windows 8 store app

PCLSample.Web : MVC4, Silverlight host, DataService host

PCLSample.WPF : WPF

--Windows Phone 7.1 프로젝트는 제외했다.(PCL 구성을 Windows Phone 8이상으로 변경했기 때문이다.)

 

4. Base

EntityFramework - Code-First

Repository Pattern

MVVM

Portable Class Library

WebAPI

IoC (Inversion of control) : http://en.wikipedia.org/wiki/Inversion_of_control

 

5. 셈플에 구현된 내용

이번 포스트에서는 하나의 DataService를 만들어 놓고, 각 플랫폼에서 조회하는 부분까지만 구현되어 있다.

Model : PersonModel

Mapping : PersonModelMap.cs , PCL 프로젝트에 있는 PersonModel을 Code-First로 MSSQL과 연동하도록 만듬

Repository : GenericRepository.cs, 기본적인 레파지토리

UnitOfWork : UnitOfWork.cs, 전체 레파지토리 관리

DbContext : PCLSampleContext.cs

 

6. PCLSample.DAL

.Net Framework 4.5, Class Library

 

Nuget packages

Microsoft.Bcl.Async  //모든 프로젝트에 공통으로 포함된다.

Microsoft.Bcl.Build    //모든 프로젝트에 공통으로 포함된다.

Microsoft.Bcl            //모든 프로젝트에 공통으로 포함된다.

Microsoft.Net.Http     //모든 프로젝트에 공통으로 포함된다.

EntityFramework

 

6-1. PCLSampleContext

EntityFramework를 Code-First로 사용하기 위한 기본 클래스

 

    /// <summary>
    /// DbContext
    /// </summary>
    public class PCLSampleContext : DbContext
    {
        /// <summary>
        /// 생성자
        /// </summary>
        public PCLSampleContext()
            : base("name=PCLSampleContext")
        {
        }

        /// <summary>
        /// People Table
        /// </summary>
        public DbSet<PersonModel> People { get; set; }

        /// <summary>
        /// OnModelCreating
        /// </summary>
        /// <param name="modelBuilder"></param>
        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            modelBuilder.Configurations.Add(new PersonModelMap());
            base.OnModelCreating(modelBuilder);
        }
    }

 

6-2. PCLSampleContextInitializer

모델 생성시 초기화 모듈 - 여기서는 Seed값 입력

 

    public class PCLSampleContextInitializer : System.Data.Entity.DropCreateDatabaseIfModelChanges<PCLSampleContext>
    {
        /// <summary>
        /// Seed
        /// </summary>
        /// <param name="context"></param>
        protected override void Seed(PCLSampleContext context)
        {
            var people = new List<PersonModel>
                {
                    new PersonModel{ Id = 0, Name = "kaki104", Age = 20, Sex = true },
                    new PersonModel{ Id = 1, Name = "kaki105", Age = 21, Sex = false },
                    new PersonModel{ Id = 2, Name = "kaki106", Age = 22, Sex = true },
                    new PersonModel{ Id = 3, Name = "kaki107", Age = 23, Sex = false },
                    new PersonModel{ Id = 4, Name = "kaki108", Age = 24, Sex = true },
                };
            people.ForEach(p => context.People.Add(p));

            context.SaveChanges();

            base.Seed(context);
        }
    }

6-3. PersonModelMap

PersonModel의 맵핑 클래스

 

    /// <summary>
    /// PersonModel Mapping class
    /// </summary>
    class PersonModelMap : System.Data.Entity.ModelConfiguration.EntityTypeConfiguration<PersonModel>
    {
        public PersonModelMap()
        {
            this.HasKey(f => f.Id);

            this.ToTable("People");
            this.Property(f => f.Id)
                .IsRequired()
                .HasColumnName("Id");
            this.Property(f => f.Name)
                .HasMaxLength(50)
                .HasColumnName("Name");
            this.Property(f => f.Age)
                .HasColumnName("Age");
            this.Property(f => f.Sex)
                .HasColumnName("Sex");
        }
    }

 

6-4. GenericRepository

기본 레파지토리 클래스

 

public class GenericRepository<TEntity> : PCLSample.DAL.Repositories.IGenericRepository<TEntity> where TEntity : class
    {
        /// <summary>
        /// DbContext
        /// </summary>
        private DbContext _context;
        /// <summary>
        /// IDbSet
        /// </summary>
        private readonly IDbSet<TEntity> _dbSet;

        /// <summary>
        /// 생성자
        /// </summary>
        /// <param name="context"></param>
        public GenericRepository(DbContext context)
        {
            _context = context;
            _dbSet = _context.Set<TEntity>();
        }

        /// <summary>
        /// 전체 조회 - WebAPI에서 사용
        /// </summary>
        /// <returns></returns>
        public virtual IQueryable<TEntity> AllGetting()
        {
            return _dbSet.AsQueryable();
        }

        /// <summary>
        /// 조회
        /// </summary>
        /// <param name="filter">조회조건</param>
        /// <param name="orderBy">정렬조건</param>
        /// <param name="includeProperties">Include속성</param>
        /// <returns></returns>
        public virtual IQueryable<TEntity> Getting(
            System.Linq.Expressions.Expression<Func<TEntity, bool>> filter = null,
            Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy = null,
            string includeProperties = "",
            int? page = null,
            int? pageSize = null)
        {
            IQueryable<TEntity> query = _dbSet.AsQueryable();

            //조건절이 있으면 추가
            if (filter != null)
            {
                query = query.Where(filter);
            }

            //Include가 있으면 추가
            foreach (var includeProperty in includeProperties.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
            {
                query = query.Include(includeProperty);
            }

            //정렬값이 있으면 정렬하고 아니면 그냥 보내고..
            if (orderBy != null)
            {
                orderBy(query).AsQueryable();
            }

            if (page != null && pageSize != null)
            {
                int pageNo = Convert.ToInt32(page) - 1;
                int pSize = Convert.ToInt32(pageSize);

                if (pageSize <= 0)
                {
                    pageSize = 10; // default
                }

                if (pageNo <= 0)
                {
                    pageNo = 0;
                }

                int skipSize = pageNo * pSize;

                return query.Skip(skipSize).Take(pSize).AsQueryable();
            }
            else
            {
                return query.AsQueryable();
            }
        }

        /// <summary>
        /// 키값을 가지고 조회
        /// </summary>
        /// <param name="id">키값</param>
        /// <returns></returns>
        public virtual TEntity GettingByID(object id)
        {
            return _dbSet.Find(id);
        }

        /// <summary>
        /// Entity를 받아와서 저장
        /// </summary>
        /// <param name="entity"></param>
        public virtual void Insert(TEntity entity)
        {
            _dbSet.Add(entity);
        }

        /// <summary>
        /// 키값으로 Entity를 찾아서 삭제
        /// </summary>
        /// <param name="id"></param>
        public virtual void Delete(object id)
        {
            TEntity entityToDelete = _dbSet.Find(id);
            Delete(entityToDelete);
        }

        /// <summary>
        /// Entity를 받아서 삭제
        /// </summary>
        /// <param name="entityToDelete"></param>
        public virtual void Delete(TEntity entityToDelete)
        {
            //DB에 없는 Entity를 삭제할 경우 DB에 추가한 후에 삭제
            if (_context.Entry(entityToDelete).State == EntityState.Detached)
            {
                _dbSet.Attach(entityToDelete);
            }
            _dbSet.Remove(entityToDelete);
        }

        /// <summary>
        /// Entity를 받아서 수정
        /// </summary>
        /// <param name="entityToUpdate"></param>
        public virtual void Update(TEntity entityToUpdate)
        {
            //DB에 Entity를 등록
            _dbSet.Attach(entityToUpdate);

            //context에 Entity를 등록한 후 상태를 Modified로 변경
            _context.Entry(entityToUpdate).State = EntityState.Modified;
        }

        public virtual void Detach(TEntity entityToDetach)
        {
            _context.Entry(entityToDetach).State = EntityState.Detached;
        }

        #region IDisposable
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        protected virtual void Dispose(bool disposing)
        {
            if (!disposing) return;
            if (_context == null) return;
            _context.Dispose();
            _context = null;
        }
        #endregion

    }
}

 

6-5. UnitOfWork

UnitOfWork 클래스 - 이 클래스를 통해 각 테이블에 접근

TransactionScope을 사용하기 위해서는 System.Transactions 러퍼런스를 추가해 주어야함

 

    public class UnitOfWork : IDisposable, PCLSample.DAL.Repositories.IUnitOfWork
    {
        /// <summary>
        /// 트렌젝션
        /// </summary>
        private TransactionScope _tx;
        /// <summary>
        /// KTour Context
        /// </summary>
        private DbContext _context;

        private IGenericRepository<PersonModel> _peopleRepo;
        /// <summary>
        /// PersonModel Repository
        /// </summary>
        public IGenericRepository<PersonModel> PeopleRepo
        {
            get { return _peopleRepo = _peopleRepo ?? new GenericRepository<PersonModel>(_context); }
        }

        /// <summary>
        /// 생성자
        /// </summary>
        public UnitOfWork()
        {
            _context = new PCLSampleContext();
        }

        /// <summary>
        /// 저장
        /// </summary>
        /// <returns></returns>
        public bool Save()
        {
            try
            {
                //저장~~~~~~~~~~~~~~~
                _context.SaveChanges();
                return true;
            }
            catch (DbEntityValidationException dbEx)
            {
                foreach (var validationErrors in dbEx.EntityValidationErrors)
                {
                    foreach (var validationError in validationErrors.ValidationErrors)
                    {
                        System.Diagnostics.Trace.TraceInformation("Class: {0}, Property: {1}, Error: {2}", validationErrors.Entry.Entity.GetType().FullName,
                                      validationError.PropertyName, validationError.ErrorMessage);
                    }
                }
                return false;
            }
        }

        /// <summary>
        /// Begins the transaction.
        /// </summary>
        public void BeginTransaction()
        {
            try
            {
                this._tx = new TransactionScope();
            }
            catch (Exception ex)
            {
                throw new Exception(string.Format("An error occured during the Begin transaction.\r\n{0}", ex.Message));
            }
        }

        /// <summary>
        /// Commits the transaction.
        /// </summary>
        public void CommitTransaction()
        {
            try
            {
                if (this._tx == null)
                {
                    throw new TransactionException("The current transaction is not started!");
                }
                this._tx.Complete();
            }
            catch (Exception ex)
            {
                throw new Exception(string.Format("An error occured during the Commit transaction.\r\n{0}", ex.Message));
            }
            finally
            {
                this._tx.Dispose();
            }
        }

        /// <summary>
        /// Rollbacks the transaction.
        /// </summary>
        public void RollbackTransaction()
        {
            try
            {
                if (this._tx != null)
                {
                    this._tx.Dispose();
                }
            }
            catch (Exception ex)
            {
                throw new Exception(string.Format("An error occured during the Rollback transaction.\r\n{0}", ex.Message));
            }
        }


        #region ///Dispose

        private bool disposed = false;

        protected virtual void Dispose(bool disposing)
        {
            if (!this.disposed)
            {
                if (disposing)
                {
                    if (_tx != null)
                        _tx.Dispose();
                    _context.Dispose();
                }
            }
            this.disposed = true;
        }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        #endregion
    }

이렇게 구성하면 DAL 프로젝트가 구성되며, Database와 연결하기 위해서

App.config 파일에 ConnectionString을 추가해 준다.

 

  <connectionStrings>
    <add name="PCLSampleContext" connectionString="Data Source=(local)\sqlexpress;Initial Catalog=PCLSample;Integrated Security=SSPI" providerName="System.Data.SqlClient" />
  </connectionStrings>

 

ConnectionString은 각자 컴퓨터의 환경에 맞게 지정한다.

 

Posted by MVP kaki104

게임 Start Page

http://kakisoft.com/numbers/gamepilot.aspx

 

1to50 online 설명

5x5 배열에 랜덤하게 배열된 1부터 50까지의 숫자를 차례대로 선택하는 게임입니다. 원래는 혼자서 플레이를 하는 게임인데, 이것을 온라인에서 여러명이 동시에 게임을 할 수 있도록 만들었습니다.

게임의 시작은 방을 만든 사람이 Start 버튼을 눌러서 할 수 있습니다.

혼자서하면 기록을 재는 게임이지만, 여러명이서 플레이를 하면, 약간의 긴장감이..^^;;

현재는 익명플레이로 게임이 가능하지만 차후 로그인이 가능하도록해서 기록을 남길 수 있도록 지원할 예정입니다.

 

 

개발자 이야기

게임을 처음 만들어 본 것이라..기획, 그래픽이 가미된 게임은 아닙니다.

다만, 앞으로 여러분들의 게임을 해보시고 좋은점, 나쁜점, 수정했으면 하는 부분이나, 추가했으면 하는 기능등을 알려주시면

하나씩 추가해서 좀더 완성도가 있는 게임을 만들어 보고 싶습니다.

 

추가적으로 온라인 게임 개발에 관심있는 분들이 계시면 함께 작업하는 것도 가능하니 리플로 남겨주시면 감사하겠습니다.

 

게임시스템

서버 : Windows Server 2008 / MVC4 / SignalR / WebAPI

클라이언트 : Silverlight 5

개발 환경 : Windows 8 / Visual Studio 2012

 

0.9.1 업데이트 수정사항

* 게임 시작시 카운트가 화면 가운데 표시 됩니다.

* 방 상태가 Watting이 아니면 입장이 불가능 합니다.

* 잘못된 번호를 클릭시 1초간 버튼을 선택하지 못하는 쿨다운 기능이 추가되었습니다. (다음 번호를 마구 클릭하는 것을 방지하기 위함)

 

1. Login 버튼이 활성화 될 때까지 기다립니다.

버튼이 활성화 되면 클릭해서 Login을 합니다. 아이디는 분초를 이용해서 자동 생성 됩니다.

 

 

2. Login이 완료되면 Lobby에 입장하실 수 있습니다.

로그인 후 접속되어있는 사용자 목록과, 게임룸 목록을 조회 합니다. 조회가 완료될 때까지 기다립니다.

* Create 버튼 :

모든 조회가 완료되면 Create 버튼이 활성화 됩니다.

게임룸을 생성 합니다. 생성시 룸 이름은 임의로 지정 됩니다.

게임룸 생성에 시간이 걸릴 수 있으니 기다려 주시기 바랍니다. (여러번 누르지 마세요.)

* Join 버튼 :

오른쪽 게임룸 목록에서 룸을 선택 하시면 Join 버튼이 활성화 됩니다. 

Join 버튼을 클릭해서 게임룸에 참가 하실 수 있습니다.

 

 

3. 방을 만들고 게임룸에 입장한 상태 입니다.

* Start :

게임을 시작 합니다.

* Leave :

게임룸에서 나갑니다.

게임룸에 아무도 존재하지 않을 경우 게임룸은 삭제 됩니다.

 

 

4. 게임 진행 상태 입니다.

Next에 표시되는 숫자를 찾아서 클릭 합니다.

 

 

 

5. 모든 숫자를 선택하면 결과 화면이 출력됩니다.

* OK :

결과를 확인 하고 대기 상태로 변경합니다.

참여하고 있는 모든 플레이어가 OK를 선택해야 합니다.

 

6. 개선의 여지가 많은 게임입니다.

많은 의견 부탁드립니다. 감사합니다.

 

Posted by MVP kaki104

 1 to 50 게임 테스트 부탁드립니다.
게임 방법 : 1부터 50까지의 숫자를 순서대로 클릭하면 됩니다.
게임에 접속하면 왼쪽에 접속자 목록이 보이고, 그중에 Master라고 되어있는 사용자만 게임을 시작/종료 할 수 있습니다.

http://kakisoft.com/numbers/gamepilot.aspx


 

 

 

 

 

 

사용해보시고 의견 남겨주시면 감사하겠습니다.

 

사용기술
MVC4, .Net Framework 4.5
SignalR 1.0.1
Silverlight 5

 

Posted by MVP kaki104

시그널알 시작하기 페이지

http://www.asp.net/signalr/overview/getting-started/tutorial-getting-started-with-signalr

 

시그널알 소스 페이지

https://github.com/SignalR/SignalR

 

시그널알 Wiki 페이지

https://github.com/SignalR/SignalR/wiki

 

시그널알 NDC 동영상

http://vimeo.com/43659069

 

ElmahR = ELMAH + SignalR

http://www.codeproject.com/Articles/377394/ElmahR-equals-ELMAH-plus-SignalR

Posted by MVP kaki104

티스토리 툴바