Tuesday, February 2, 2016

Typing data returned from a WebAPI

I'm a huge fan of where modern web development is headed.  My current technology stack is ASP.NET MVC 5 & WebAPI 2 using ASP.NET Identity to provide social integration.  On the client, I really like Knockout.js because it handles just model binding.  Currently, I also use jQuery for it's promises and ajax, but I think there is probably a better (more light-weight) solution for just that and I will be looking to replace it.

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 types 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 implements 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