티스토리 뷰
WebAPI CRUD Sample - Framework 4.5, MVC4, Windows 8 RTM app base
kaki104 2012. 8. 20. 16:16WebAPI의 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/
Cross Domain RESTful CRUD Operations using jQuery
http://www.matlus.com/
CRUD operation using ASP.NET Web Api and MVC 4 – Part 1
http://www.dotnetglobe.com/
CRUD operation using ASP.NET Web Api and MVC 4 – Part 2
http://www.dotnetglobe.com/
ASP.NET Web API CRUD Operations
http://www.c-sharpcorner.com/
ASP.NET WebAPI: Getting Started with MVC4 and WebAPI
http://www.codeproject.com/
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를 한번 하고 빌드하기를 권함
7. 역시 모르면 어렵지만
알면 이렇게 쉬운것을..
'Previous Platforms > HTML5 & MVC4' 카테고리의 다른 글
Web Api Routing for multiple Get methods in ASP.NET MVC 4 (0) | 2013.02.28 |
---|---|
SignalR을 사용한 영화 예매 시스템 구현 (2) | 2012.11.02 |
WebAPI OData query sample - Framework 4.5, MVC4, Windows 8 RP, RTM app base (0) | 2012.09.07 |
MVC4 tips (0) | 2012.08.31 |
MVC4 - Single Page Application (SPA) (2) | 2012.04.18 |
- Total
- Today
- Yesterday
- .net
- LINQ
- .net 5.0
- visual studio 2019
- MVVM
- #prism
- WPF
- IOT
- uno platform
- ComboBox
- #Windows Template Studio
- Bot Framework
- Build 2016
- Always Encrypted
- C#
- kiosk
- Cross-platform
- PRISM
- Behavior
- XAML
- #MVVM
- #uwp
- Windows 10
- dotNETconf
- Visual Studio 2022
- UWP
- windows 11
- Microsoft
- ef core
- uno-platform
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 |