While I have been interviewing, I have noticed that Angular often comes up while talking about ASP.NET MVC and that drives me crazy, crazy. Firstly, if you are going Angular, why are you using ASP.NET MVC with IIS? I like having ASP.NET handling my routing, security, data validations, ... and let knockout handle just the model binding on the front end. If you are using Angular just for model binding, I would image you are pulling in way more than needed. Similar to my using jQuery for just the ajax / promises :)
With that out of the way, I can focus on the real task at hand....
How to handle data returned from a WebAPI
Ok, so that's a broad topic. Let's narrow it down to a few categories:
Display Only Data
This type of data is only used to display something to the user. They are not going to manipulate this data.
In this case, I have seen examples of folks generating interfaces for the data and using that where needed. An example would look like:
C#
public class MyListItem { public int Id { get; set; } public string Title { get; set; } }
TypeScript
interface IMyListItem { Id: number; Title: string; }
However, I prefer to use
type
s instead.
type MyListItemInfo = { Id: number; Title: string; };
This seems to be a more appropriate indication of the expectation of the data.
If I were to call some ApiController method that returned a list of these, it may look something like this:
[HttpGet]
[Route("")]
public async Task<MyListItem[]> GetItems()
{
return await this.Request.GetOwinContext().Get<ApplicationDbContext>().Items.ToArrayAsync();
}
//[{Id:1, Title:"Red Hat"}]
GetItems() : JQueryPromise<MyListItem[]> {
return $.getJSON("http://mysite.com/api/items/");
}
At this point, my javascript code has to hope for the best. It is being handed JSON from some external source which may not be under our control. Indicating that it is some type which the designated shape better defines the data. If we were to return it as an interface, that may indicate that we have ensured the created javascript code actually
The following would be an appropriate use of interfaces:
If I were to call some ApiController method that returned a list of these, it may look something like this:
[HttpGet]
[Route("")]
public async Task<MyListItem[]> GetItems()
{
return await this.Request.GetOwinContext().Get<ApplicationDbContext>().Items.ToArrayAsync();
}
//[{Id:1, Title:"Red Hat"}]
GetItems() : JQueryPromise<MyListItem[]> {
return $.getJSON("http://mysite.com/api/items/");
}
At this point, my javascript code has to hope for the best. It is being handed JSON from some external source which may not be under our control. Indicating that it is some type which the designated shape better defines the data. If we were to return it as an interface, that may indicate that we have ensured the created javascript code actually
implement
s the interface.
The following would be an appropriate use of interfaces:
class MyListItem implements IMyListItem {
Id: number;
Title: string;
// prefer to accept the type when loaded from external
constructor(initial?:IMyListItem/*MyListItemInfo*/){
this.Id = initial && initial.Id;
this.Title = initial && initial.Title;
}
};
class ItemManagementViewModel{
private urlBase: string;
constructor(urlBase: string){
this.urlBase = urlBase;
}
// actually implements IMyListItem
GetItems() : JQueryPromise<IMyListItem[]>{
return $.getJSON(this.urlBase)
.then(result => $.map(result,
r => new MyListItem(r)));
}
}
Read / Write Data
This type of data is meant to be downloaded and presented to the user for possible manipulation. In this case, I'd like to have a editable version of the object.
class MyListItemEdit {
// cannot edit Id; it's an identifier for what we have just edited
Id: number;
Title: KnockoutObservable<string>;
constructor(initial?: MyListItemInfo){
this.Id = initial && initial.Id;
this.Title = ko.observable(initial && initial.Title);
}
}
[HttpPost]
[Route("save")]
public async Task<int> SaveItem([FromBody]MyListItem toSave)
{
var item = await
this.Request
.GetOwinContext()
.Get<ApplicationDbContext>()
.Items
.FirstOrDefaultAsync(x => x.Id == toSave.Id);
if( null == item ){ throw new ArgumentException(); }
item.Title = toSave.Title;
return await this.Request
.GetOwinContext()
.Get<ApplicationDbContext>().SaveChangesAsync();
}
// notice that we have to project into the edit type
// this is why I prefer the edit type to take a type
// and not an interface
GetItemsForEdit() : JQueryPromise<MyListItemEdit[]>{
return $.getJSON(this.urlBase)
.then(result => $.map(result, r => new MyListItemEdit(r)));
}
SaveItem(toSave: MyListItemEdit) : JQueryPromise<number>{
return $.ajax({
url: this.urlBase + 'save',
method: 'POST',
contentType: 'application/json',
dataType: 'json',
data: JSON.stringify(ko.toJS(toSave))
});
}
To this end, I was about to create a tool to generate these typescript files from C# types, but I stumbled across this article regarding generating these files using TypeLite. I like that it was built on top of T4 templates and will be giving this a shot. I am going to try an manipulate the template to generate types instead of interfaces and to create edit types that take the type as constructor argument.
No comments:
Post a Comment