티스토리 뷰

반응형

WebAPI의 Datetime 문제 때문에 일주일 정도 고생을 해서 결국은 다 헤집어서 찾아 냈다. 다시 생각해보면 정말 바보 같다는 생각이..ㅜㅜ 왜 그걸 생각 못했는지...

 

결국 문제는 클라이언트에서 사용한 DataContractJsonSerializer가 Data Type을 이미 알고 있는데도 불구하고, 변환을 시키지 못하는 버그가 존재 한다는 것이다. 그래서. 이걸 사용하면 Datetime을 Deserialize 할 수 없는데도..서버에서 왜 "/Date(" 이렇게 않 넘겨주냐고 혼자 고민하고 있었다는..

 

이제 최종판을 올리도록 하겠다.

 

* 다음 포스트

WebAPI OData query sample - Framework 4.5, MVC4, Windows 8 RP, RTM app base

http://kaki104.tistory.com/145

 

1. 개발환경

Windows 8 RTM

Visual Studio 2012 RTM

MVC 4, WebAPI project

Windows Store project - Metro style app C#

Portable Class Library project

Newtonsoft.Json v4.5 - Nuget install

 

참고 포스트

Getting Started with ASP.NET Web API
http://www.asp.net/web-api

Consuming ASP.NET Web API in a Metro Style Application using jQuery
http://www.dotnetcurry.com/ShowArticle.aspx?ID=817


Cross Domain RESTful CRUD Operations using jQuery
http://www.matlus.com/cross-domain-restful-crud-operations-using-jquery/

CRUD operation using ASP.NET Web Api and MVC 4 – Part 1
http://www.dotnetglobe.com/2012/03/crud-operation-using-aspnet-web-api-in.html

CRUD operation using ASP.NET Web Api and MVC 4 – Part 2
http://www.dotnetglobe.com/2012/03/crud-operation-using-aspnet-web-api-in_28.html

ASP.NET Web API CRUD Operations
http://www.c-sharpcorner.com/UploadFile/raj1979/Asp-Net-web-api-crud-operations/

ASP.NET WebAPI: Getting Started with MVC4 and WebAPI
http://www.codeproject.com/Articles/344078/ASP-NET-WebAPI-Getting-Started-with-MVC4-and-WebAP

 

 

 

 

2. Model

Web과 App에서 동시에 사용할 수 있어야 하기 때문에 Portable에 모델을 만들어 놓았다.

 

namespace WebApiSample.Models
{
    public class Person
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public int Age { get; set; }
        public DateTime CreateDate { get; set; }
    }
}

 

3. Web 구성

PersonController를 추가했다. 추가하면 PersonContext.cs도 생성되고, 여기저기에 몇가지가 더 추가된다.

 

namespace WebApiSample.Controllers
{
    public class PersonController : ApiController
    {
        private PersonContext db = new PersonContext();

        public IQueryable<Person> GetPeople()
        {
            return db.People;
        }

        public Person GetPerson(int id)
        {
            Person person = db.People.Find(id);
            if (person == null)
            {
                throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotFound));
            }

            return person;
        }

        public HttpResponseMessage PutPerson(int id, Person person)
        {
            if (ModelState.IsValid && id == person.Id)
            {

                //수정시간을 넣을려고 했는데, 아마도, 동작이 않되는 것 같다. 디버그가 않되서 확인을 못함
                person.CreateDate = DateTime.UtcNow;

                db.Entry(person).State = EntityState.Modified;

                try
                {
                    db.SaveChanges();
                }
                catch (DbUpdateConcurrencyException)
                {
                    return Request.CreateResponse(HttpStatusCode.NotFound);
                }

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

        public HttpResponseMessage PostPerson(Person person)
        {
            if (ModelState.IsValid)
            {

                //생성 시간 입력
                person.CreateDate = DateTime.UtcNow;

                db.People.Add(person);
                db.SaveChanges();

                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);
            }
        }

        public HttpResponseMessage DeletePerson(int id)
        {
            Person person = db.People.Find(id);
            if (person == null)
            {
                return Request.CreateResponse(HttpStatusCode.NotFound);
            }

            db.People.Remove(person);

            try
            {
                db.SaveChanges();
            }
            catch (DbUpdateConcurrencyException)
            {
                return Request.CreateResponse(HttpStatusCode.NotFound);
            }

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

        protected override void Dispose(bool disposing)
        {
            db.Dispose();
            base.Dispose(disposing);
        }
    }
}

 

Web.config, connectionString

<add name="PersonContext" connectionString="data source=|DataDirectory|PersonDB.sdf" providerName="System.Data.SqlServerCe.4.0" />

 

실행해 보자

 

주소창에 /api/person을 입력 한 후 엔터를 치면 

 

person.json이란 파일을 열것인지를 물어 본다. Open을 클릭하고, Notepad를 선택 하자

 

방금 서버에 있는 Person의 목록을 모두 불러 온다.(db에 데이터가 없으면 []만 보인다)

Web project의 상세한 구성은 소스를 참고하기 바란다.

 

4. App

 

적당한 Windows Store project를 추가 한 후 Manage NuGet Packages를 선택 하여, Json.Net을 인스톨 한다.

 

 

MainPageView.xaml

아래 그리드 뷰 코드만 올린다.

...

<GridView x:Name="itemGridView" Grid.Row="1" Margin="120,0,50,50" ItemsSource="{Binding People}" ItemTemplate="{StaticResource PersonDataTemplate}" IsItemClickEnabled="True" />

...

 

MainPageView.xaml.cs

 

    public sealed partial class MainPageView : WebApiSample.App.Common.LayoutAwarePage
    {
        public MainPageViewModel ViewModel
        {
            get { return this.DataContext as MainPageViewModel; }
            set
            {
                this.DataContext = value;
            }
        }

        public MainPageView()
        {
            this.InitializeComponent();

            //뷰 모델 생성

            ViewModel = new MainPageViewModel();
        }


        protected override void LoadState(Object navigationParameter, Dictionary<String, Object> pageState)
        {

            //셀렉션체이지 이벤트를 뷰 모델에 연결
            itemGridView.SelectionChanged += ViewModel.SelectionChanged;
        }

        protected override void SaveState(Dictionary<String, Object> pageState)
        {
            itemGridView.SelectionChanged -= ViewModel.SelectionChanged;
        }
    }

 

MainPageViewModel.cs

 

    public class MainPageViewModel : BindableBase
    {
        private const string ApiRoot = "http://localhost:11666//api/person";

        private ObservableCollection<Person> people;
        /// <summary>
        /// 피플
        /// </summary>
        public ObservableCollection<Person> People
        {
            get { return people; }
            set
            {
                people = value;
                OnPropertyChanged();
            }
        }

        private Person currentPerson;
        /// <summary>
        /// 선택한 사람
        /// </summary>
        public Person CurrentPerson
        {
            get { return currentPerson; }
            set
            {
                currentPerson = value;
                OnPropertyChanged();
            }
        }

        //생성자
        public MainPageViewModel()
        {
            People = new ObservableCollection<Person>();

            if (Windows.ApplicationModel.DesignMode.DesignModeEnabled == true)
            {
                //디자인 소스
                People.Add(new Person { Id = 1, Name = "kaki104", Age = 11, CreateDate = DateTime.Now });
                People.Add(new Person { Id = 2, Name = "kaki105", Age = 22, CreateDate = DateTime.Now.AddDays(-1) });
                People.Add(new Person { Id = 3, Name = "kaki106", Age = 33, CreateDate = DateTime.Now.AddDays(-2) });
                People.Add(new Person { Id = 4, Name = "kaki107", Age = 44, CreateDate = DateTime.Now.AddDays(-3) });
                People.Add(new Person { Id = 5, Name = "kaki108", Age = 55, CreateDate = DateTime.Now.AddDays(-4) });
            }
            else
            {
                GetAllData();
            }
        }

        /// <summary>
        /// 셀렉션 체이지 이벤트
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        public void SelectionChanged(object sender, Windows.UI.Xaml.Controls.SelectionChangedEventArgs e)
        {
            if (e.RemovedItems.Count > 0)
            {
                CurrentPerson = null;
            }

            if (e.AddedItems.Count > 0)
            {
                CurrentPerson = e.AddedItems.FirstOrDefault() as Person;
            }
        }

        /// <summary>
        /// 전체 데이터 조회
        /// </summary>
        private async void GetAllData()
        {

            //http 설정
            var http = new HttpClient();

            //전체 데이터 조회
            var resp = await http.GetAsync(new Uri(ApiRoot));
            if(resp.IsSuccessStatusCode)
            {

                //성공이면 반환데이터 읽어서 JsonConvert로 Object로 변환
                var result = await resp.Content.ReadAsStringAsync();
                People = JsonConvert.DeserializeObject(result, typeof(ObservableCollection<Person>)) as ObservableCollection<Person>;
            }
        }

        private DelegateCommand addCommand;
        /// <summary>
        /// 추가 커맨드
        /// </summary>
        public DelegateCommand AddCommand
        {
            get
            {
                if (addCommand == null)
                {
                    addCommand = new DelegateCommand(
                        async _ =>
                        {
                            //사람 추가
                            var addPerson = new Person();
                            addPerson.Name = "New";
                            addPerson.Age = 0;
                            //json으로 변환
                            var json = JsonConvert.SerializeObject(addPerson);

                            //http 설정
                            var http = new HttpClient();
                            var sc = new StringContent(json, Encoding.UTF8, "application/json");
                            //Post = Insert
                            var resp = await http.PostAsync(new Uri(ApiRoot), sc);

                            if (resp.IsSuccessStatusCode)
                            {
                                //성공시 반환된 데이터를 컬렉션에 추가
                                var result = await resp.Content.ReadAsStringAsync();
                                addPerson = JsonConvert.DeserializeObject(result, typeof(Person)) as Person;
                                People.Add(addPerson);

                                MsgBox("Success Add");
                            }
                            else
                            {
                                MsgBox("Fail Add");
                            }

                        });
                }
                return addCommand;
            }
        }

        private DelegateCommand removeCommand;
        /// <summary>
        /// 삭제 커맨드
        /// </summary>
        public DelegateCommand RemoveCommand
        {
            get
            {
                if (removeCommand == null)
                {
                    removeCommand = new DelegateCommand(
                        async _ =>
                        {
                            if (CurrentPerson != null)
                            {
                                //http 설정
                                var http = new HttpClient();
                                //Delete
                                var resp = await http.DeleteAsync(new Uri(ApiRoot + "/" + CurrentPerson.Id));

                                if (resp.IsSuccessStatusCode)
                                {
                                    //성공시 데이터 컬렉션에서 삭제
                                    People.Remove(CurrentPerson);
                                    CurrentPerson = null;

                                    MsgBox("Success Remove");
                                }
                                else
                                {
                                    MsgBox("Fail Remove");
                                }

                            }
                        });
                }
                return removeCommand;
            }
        }

        private DelegateCommand saveCommand;
        /// <summary>
        /// 저장 커맨드
        /// </summary>
        public DelegateCommand SaveCommand
        {
            get
            {
                if (saveCommand == null)
                {
                    saveCommand = new DelegateCommand(
                        async _ =>
                        {
                            if (CurrentPerson != null)
                            {
                                //현재 사람을 json으로 변환
                                var json = JsonConvert.SerializeObject(CurrentPerson);

                                //http 설정
                                var http = new HttpClient();
                                var sc = new StringContent(json, Encoding.UTF8, "application/json");
                                //Put = update
                                var resp = await http.PutAsync(new Uri(ApiRoot + "/" + CurrentPerson.Id), sc);

                                if (resp.IsSuccessStatusCode)
                                {
                                    //성공시 반환 데이터를 현재 사람에 입력
                                    var result = await resp.Content.ReadAsStringAsync();
                                    CurrentPerson = JsonConvert.DeserializeObject(result, typeof(Person)) as Person;

                                    MsgBox("Success Save");
                                }
                                else
                                {
                                    MsgBox("Fail Save");
                                }
                            }
                        });
                }
                return saveCommand;
            }
        }

        //메시지 출력
        public void MsgBox(string message)
        {
            MessageDialog msg = new MessageDialog(message, "Notification");
            var result = msg.ShowAsync();
        }
    }

 

실행

아이템을 선택하고, 엡바를 보이게 한 후 Save 버튼을 클릭하면, 수정되 내용이 저장된다. (삭제도 선택 후 작업) 

 

편집하는 화면을 따로 만들기 귀찮아서 템플릿 자체적으로 수정 가능 하도록 만들었다.

 

5. Bonus

Datetime 형 중에 변환이 되지 않는 것이 있는데(ISO 8601) 그 문제를 해결 해주는 코드도 .Net Framework 4.5로 변환해서 올린다.

 

JsonNetFormatter.cs

 

    /// <summary>
    /// .Net Framework 4.5 Windows 8 RTM version test
    /// </summary>
    public class JsonNetFormatter : MediaTypeFormatter
    {
        private JsonSerializerSettings _jsonSerializerSettings;

        public JsonNetFormatter(JsonSerializerSettings jsonSerializerSettings)
        {
            _jsonSerializerSettings = jsonSerializerSettings ?? new JsonSerializerSettings();

            // Fill out the mediatype and encoding we support
            SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/json"));
            SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/json"));
            SupportedEncodings.Add(new UTF8Encoding(false, true));
        }

        public override Task<object> ReadFromStreamAsync(Type type, Stream readStream, System.Net.Http.HttpContent content, IFormatterLogger formatterLogger)
        {
            // Create a serializer
            JsonSerializer serializer = JsonSerializer.Create(_jsonSerializerSettings);

            // Create task reading the content
            return Task.Factory.StartNew(() =>
            {
                using (StreamReader streamReader = new StreamReader(readStream, new UTF8Encoding(false, true)))
                {
                    using (JsonTextReader jsonTextReader = new JsonTextReader(streamReader))
                    {
                        return serializer.Deserialize(jsonTextReader, type);
                    }
                }
            });
        }

        public override Task WriteToStreamAsync(Type type, object value, Stream writeStream, System.Net.Http.HttpContent content, TransportContext transportContext)
        {
            // Create a serializer
            JsonSerializer serializer = JsonSerializer.Create(_jsonSerializerSettings);

            // Create task writing the serialized content
            return Task.Factory.StartNew(() =>
            {
                using (StreamWriter streamWriter = new StreamWriter(writeStream, new UTF8Encoding(false, true)))
                {
                    using (JsonTextWriter jsonTextWriter = new JsonTextWriter(streamWriter))
                    {
                        serializer.Serialize(jsonTextWriter, value);
                    }
                }
            });
        }

        public override bool CanReadType(Type type)
        {
            //if (type == typeof(JsonValue) || type == typeof(JsonObject) || type == typeof(JsonArray))
            //    return false;
            return true;

        }

        public override bool CanWriteType(Type type)
        {
            return true;
        }
    }

 

WebApiConfig.cs

 

    public static class WebApiConfig
    {
        public static void Register(HttpConfiguration config)
        {
            config.Routes.MapHttpRoute(
                name: "DefaultApi",
                routeTemplate: "api/{controller}/{id}",
                defaults: new { id = RouteParameter.Optional }
            );

            // Create Json.Net formatter serializing DateTime using the ISO 8601 format
            JsonSerializerSettings serializerSettings = new JsonSerializerSettings();
            serializerSettings.Converters.Add(new IsoDateTimeConverter());
            config.Formatters[0] = new JsonNetFormatter(serializerSettings);
        }
    }

 

6. 소스

 10메가 제한이 있어서 파일을 2개로 나누어서 올림, 소스를 실행전에 Clean Project를 한번 하고 빌드하기를 권함

packages.zip

 

WebApiSample.zip

 

7. 역시 모르면 어렵지만

알면 이렇게 쉬운것을..

반응형
댓글