AngulaNetBlend

Scaffolding all the client side models under an AngularJS application could be a laborious task. Even more laborious could become if we are to make changes to our model server side, and then have those changes be reflected client side. Now Imagine you are to have a good number of these models under a single application, moreover you are the only developer making this application a reality.

The technique exposed here is not always applicable. Nonetheless, It could be utilized in some application which could end up saving a great degree of time and heartache. In my scenario, the application utilized a NoSQL database on the back end. For most applications with this nature (NoSQL Databases such as RavenDB, MongoDB or DynamoDB) It is easy to implement what I am about to discuss.

AngularJS Client Side Behavior

In order to illustrate how this application behave in an AngularJS upon a request  server side, I have created the following Plunker. A emulator Angular service, emulates the request to the server

View Code In Plunker

No need to let AngularJS know your exact server side model

In a big application like the one I have defined and when the scenario is applicable. Thanks to.NET reflection, there is no need to let AngularJS know about the exact model existing server side. We rather let AngularJS know just enough so it would render the view appropriately.

Since this technique would prevent us to tell AngularJS about the exact model, then we could end up with a Controller Fits All (CFA) and A View Fits All (VFA).

The Model Feel.

For a model in a NoQL database with the following model tree.

   1: public class Member
   2: {
   3:     // MongoDB.Bson.ObjectId member
   4:     public ObjectId _id { get; set; }
   5:     public MemberInformation MemberData { get; set; }
   6: }
   7: public class MemberInformation
   8: {
   9:     public string Name { get; set; }
  10:     public string LastName { get; set; 
  11:     public DateTime Member_Since { get; set; }
  12:     public string Email { get; set; }
  13:     public int ZipCode { get; set; }
  14: }

In the example for this blog post however, I have created the following model.

   1: public class MarketValueItem
   2: {
   3:     public MarketValueItem()
   4:     {
   5:         id = Guid.NewGuid();
   6:         MarketValue = new MarketValue();
   7:     }
   8:     // would be a Bson object on MongoDB Data Base
   9:     public Guid id { get;  set; }
  10:     public MarketValue MarketValue { get; set; }
  11:  
  12: }
  13: public class MarketValue
  14: {
  15:     [Description("Money Market Fund")]
  16:     public double MoneyMarketFund { get; set; }
  17:     [Description("Common Stocks")]
  18:     public double CommonStocks { get; set; }
  19:     [Description("Stock Mutual Fund")]
  20:     public double StockMutualFund { get; set; }
  21:     [Description("Bond Fund")]
  22:     public double BondFund { get; set; }
  23:     [Description("As of")]
  24:     public DateTime ValueDate { get; set; }
  25:  
  26: }

Angular Model Creator

Thus, we embark in the journey of creating a flexible model. A model fits all that will take information all the field information that we will then bind to Angular. This is the only model that angular would now about.

   1: public class FieldInformation
   2: {
   3:     // the name of the field as to be presented to the view
   4:     public string Label { get; set; }
   5:     // the intrinsic or real name of the field as to be serializable.
   6:     public string FieldName { get; set; }
   7:     // the actual value of the field and as to be serializable 
   8:     public object FieldValue { get; set; }
   9:     // the input type, as to help Angular learn what type of input to render
  10:     public string inputType { get; set; }
  11: }

Model Factory

Subsequently to querying our data, we move to produce data with the above model properties. .NET reflection is here our great helper.

First We iterate through the properties of any model, that we are to pass to the below static Function

   1: public static List<FieldInformation> ExtractFields(object ob)
   2: {
   3:     List<FieldInformation> fields = new List<FieldInformation>();
   4:     var properties = ob.GetType().GetProperties();
   5:     foreach (PropertyInfo propinfo in properties)
   6:     {
   7:         fields.Add(ExtractFieldInfo(propinfo, ob));
   8:     }
   9:     return fields;
  10: }
   1: private static FieldInformation ExtractFieldInfo(PropertyInfo propinfo, object ob)
   2: {
   3:      FieldInformation info = new FieldInformation 
   4:         {   FieldName = propinfo.Name, 
   5:             Label = ExtractFieldDescription(propinfo), 
   6:             FieldValue = ExtractFieldValue(propinfo, ob), 
   7:             inputType = InputTypes[propinfo.PropertyType] 
   8:         };
   9:     return info;
  10: }
   1: private static string ExtractFieldDescription(PropertyInfo info)
   2: {
   3:     string description = string.Empty;
   4:     var attrs = info.GetCustomAttributes(true);
   5:     var attr = attrs.Where(g => g is DescriptionAttribute)
   6:         .Select(m => m as DescriptionAttribute).FirstOrDefault();
   7:     description = attr.Description;
   8:     return description;
   9: }
   1: private static object ExtractFieldValue(PropertyInfo info, object ob)
   2: {
   3:     return info.GetValue(ob);
   4: }
   1: private static Dictionary<Type, string> InputTypes
   2: {
   3:     get
   4:     {   var inputs = new Dictionary<Type, string>();
   5:         inputs.Add(typeof(double), "number");
   6:         inputs.Add(typeof(bool), "checkbox");
   7:         inputs.Add(typeof(DateTime), "date");
   8:         inputs.Add(typeof(string), "text");
   9:         inputs.Add(typeof(int), "number");
  10:         return inputs;
  11:     }
  12: }

Finally, We are Done!

In an MVC we can pass the result of the transformed model

   1: private List<VMITEM> Funds
   2: {
   3:     get
   4:     {   string datapath = Path.Combine(Server.MapPath("~/Data").ToString(), "Portfolio.TXT");
   5:         var q = new QueryData().RetrieveFunds(datapath);
   6:         var list = q.Select(g => new VMITEM { MapId = g.id.ToString(), 
   7:         // calling the model transformer below
   8:         fields = Worker.AngModelCreator.Creator.ExtractFields(g.MarketValue) }).ToList<VMITEM>();
   9:         return list;
  10:     }
  11: }
   1: public ActionResult ExtractPortfolio()
   2: {
   3:     // I serialized with JsonConvert only to properly serialize Date Formats
   4:     return Json(JsonConvert.SerializeObject(Funds), JsonRequestBehavior.AllowGet);
   5: }

The resulted JSON string

The following will pass a json string as the below

   1: [{
   2:           "MapId": "af1d7bd8-5378-4b62-89ea-220c2dd22764",
   3:           "fields": [{
   4:             "Label": "Money Market Fund",
   5:             "FieldName": "MoneyMarketFund",
   6:             "FieldValue": 27000,
   7:             "inputType": "number"
   8:           }, {
   9:             "Label": "Common Stocks",
  10:             "FieldName": "CommonStocks",
  11:             "FieldValue": 52000,
  12:             "inputType": "number"
  13:           }, {
  14:             "Label": "Stock Mutual Fund",
  15:             "FieldName": "StockMutualFund",
  16:             "FieldValue": 128000,
  17:             "inputType": "number"
  18:           }, {
  19:             "Label": "Bond Fund",
  20:             "FieldName": "BondFund",
  21:             "FieldValue": 53000,
  22:             "inputType": "number"
  23:           }, {
  24:             "Label": "As of",
  25:             "FieldName": "ValueDate",
  26:             "FieldValue": "2006-12-10T00:00:00",
  27:             "inputType": "date"
  28:           }]
  29:         },
  30:         .......

AngularJS, The view

Given the above JSON string, we could construct a view in the following manner:

   1: <div class="col-sm-9" >
   2:    <div class="row">
   3:    
   4:         <button type="button" class="btn btn-danger" ng-click="back()">
   5:          << Back
   6:        </button>
   7:    
   8:        <form class="form-horizontal" role="form">
   9:              <div class="form-group"  ng-repeat="Field in Model.fields">
  10:                  <div class="col-sm-3">
  11:                    <label for="inputfield" class="control-label">{{ Field.Label }}</label>
  12:                   </div>
  13:                  <div class="col-sm-9" >
  14:                      <input  type="{{Field.inputType}}" class="form-control" id="inputfield" ng-model="Field.FieldValue" placeholder="{{ Field.Label }}" />
  15:                 </div>
  16:              </div>
  17:              <div class="form-group">
  18:                  <div class="col-sm-3"></div>
  19:                <div class="col-sm-9">
  20:                  <div class="checkbox">
  21:                    <label>
  22:                       <button type="button"  class="btn btn-success" ng-click="Update(Model)" ng-show="!Loading">Update</button>
  23:                    </label>
  24:                  </div>
  25:                </div>
  26:              </div>  
  27:         </form>
  28:    </div>
  29:    </div>

AngularJS, The controller

In the code example, I created two types of models / records in order to prove the proper rendering of both elements in a single controller. I created a navigation view that will update the $rootScope this will then specify to Angular what type of view does the user desire to render, a “Funds” view or a “Holder” view.

   1: var AngularApp = angular.module("MultipleModelsApp", ['ngRoute']);
   2: AngularApp.factory('ViewService', function ($http, $q, $rootScope) {
   3:     var views = [{
   4:         name: 'Funds',
   5:         Title: "Fund History",
   6:         url: '/vm/ExtractPortfolio',
   7:         queurl: '/vm/FundByID',
   8:         quenewurl: '/vm/NewFund',
   9:         updateurl: '/vm/SubmitFund'
  10:     }, {
  11:         name: 'Holders',
  12:         Title: "Account Holders",
  13:         url: '/vm/ExtractHolders',
  14:         queurl: '/vm/HolderById',
  15:         quenewurl: '/vm/NewHolder',
  16:         updateurl: '/vm/SubmitHolder'
  17:     }];
  18:     return {
  19:         getViews: function () {
  20:             return views;
  21:         },
  22:         getUrl: function (type) {
  23:             var query = (type == 'query') ? "$.queurl" : (type == 'new') ? "$.quenewurl" : (type == 'update') ? "$.updateurl" : "$.url";
  24:             var view = $rootScope.view;
  25:             var url = Enumerable.From(views)
  26:                 .Where(function (x) {
  27:                 return x.name == view
  28:             })
  29:                 .Select(query)
  30:                 .FirstOrDefault();
  31:             return url;
  32:  
  33:         },
  34:         getTitle: function () {
  35:             var view = $rootScope.view;
  36:             var title = Enumerable.From(views)
  37:                 .Where(function (x) {
  38:                 return x.name == view
  39:             })
  40:                 .Select(function (x) {
  41:                 return x.Title
  42:             })
  43:                 .FirstOrDefault();
  44:             return title;
  45:         }
  46:     }
  47: });
   1: AngularApp.controller("NavCtrl", function ($scope, $rootScope, ViewService) {
   2:  
   3:     $scope.views = ViewService.getViews();
   4:     $rootScope.views = ViewService.getViews();
   5:  
   6:  
   7:     $scope.init = function () {
   8:         $rootScope.view = "Funds";
   9:     }
  10:     $scope.init();
  11:  
  12:     $scope.setView = function (view) {
  13:        if (view !== $rootScope.view) {
  14:             $rootScope.view = view;
  15:             $rootScope.$emit("App.ViewChange");
  16:         }
  17:     }
  18:  
  19: });
   1: <body ng-app="MultipleModelsApp" class="container" >
   2:     
   3:     <div class="col-sm-6" ng-controller="NavCtrl">
   4:         <div class="col-sm-3" ng-repeat="view in views">
   5:             <button type="button" class="btn btn-info" ng-click="setView(view.name)" >{{view.name}}</button>
   6:         </div>
   7:  
   8:     </div>
   9:     <div class="col-sm-12 content" ng-view="">
  10:  
  11:     </div>
  12:        
  13: </body>
   1: AngularApp.controller("ModifyCtrl", function ($scope, DataRequestPost, $routeParams, $window, $rootScope, ViewService) {
   2:    
   3:     $scope.id = $routeParams.id;
   4:  
   5:     $scope.Loading = false;
   6:  
   7:     $scope.back = function () {
   8:          $window.history.back();
   9:     }
  10:     
  11:     var retrieveModel = function () {
  12:         var url = ViewService.getUrl('query');
  13:         var params = { id: $scope.id };
  14:         DataRequestPost.get(url, params).then(function (data) {
  15:             var model = JSON.parse(data);
  16:             $scope.Model = resolvemodel(model);
  17:         });
  18:     }
  19:  
  20:     var resolvemodel = function (model) {
  21:         var fields = model.fields;
  22:         var length = fields.length;
  23:         for (var i = 0; i < length; i++) {
  24:  
  25:             if (model.fields[i].inputType === "date") {
  26:                 var day = new Date(model.fields[i].FieldValue);
  27:                 model.fields[i].FieldValue = new Date(day.getTime() + day.getTimezoneOffset() * 60000);
  28:             };
  29:         }
  30:         return model;
  31:     }
  32:  
  33:     var UpdateModel = function (model) {
  34:         $scope.Loading = true;
  35:         var url = ViewService.getUrl('update')
  36:         var params = { model: JSON.stringify(model) };
  37:         DataRequest.get(url, params).then(function (data) {
  38:             $scope.Loading = false;
  39:           
  40:         });
  41:  
  42:     }
  43:  
  44:     $scope.Update = function (model) {
  45:         UpdateModel(model);
  46:     }
  47:  
  48:     $scope.init = function () {
  49:         retrieveModel();
  50:     }
  51:     $scope.init();
  52:  
  53: });

Same View / Same Controller

funds

holder

fundentry

holderEntry

Transform Back to the .NET Server Model - On Update

Since our .NET model suffered a transformation to be rendered in Angular, We need to transform the model to its .NET representation.

   1: public static object TransformFields(List<FieldInformation> fields, object context)
   2: {
   3:     foreach (FieldInformation FieldInfo in fields)
   4:     {
   5:         context.GetType().GetProperty(FieldInfo.FieldName, System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance).SetValue(context, FieldInfo.FieldValue);
   6:     }
   7:     return context;
   8: }

Then on Submit of the below model to the server, we will have the following server side behavior

submit serverside