AngularJS

As the market more and more converge towards the one and only trustworthy client execution environment JavaScript – the need to actually DO THINGS with JavaScript force itself upon us die hard strongly typed MDriven subscribers.

There are a lot of client side frameworks/helpers out there.

What I look for in a client side helper is databinding and viewmodel handling: the ability to separate the data we work on from the templates that present it and let the user change it.

If I was forced to write a whole application in JavaScript today I would do it like this (notice how I cunningly avoid having to actually use JavaScript at all );

1. Use MDriven Framework to hold the classes and handle all the persistence and database evolution that needs to be done

2. Use ApiControllers for data that should be fetched by the client and posted back by the client

3. Use TypeScript so that I have a type-checked and compiling way to  produce my JavaScript ( I actually never touch the JavaScript code with my hands – just poke it with my TypeScript compiler )

4. Use AngularJS as the client side framework

 

So even if you have another client side framework/helper or you choose to just free base your client side – the stack above from 1 to 3 can be used. Meaning:  if you have no interest in AngularJS as such – you can still find useful things in this article.

I started with another guys sample, this guy had in turn started with yet another guys sample that wrote a little application that list and let user update objects that all the time are held on the client side as javascript prototypes. So My guy that copied the first guy introduced TypeScript – thank god – brilliant thing TypeScript is – now when this exists client side execution in Javascript may actually be one way to go. TypeScript works perfectly strongly typed with 3-party libraries like AngularJS if you have a Type Definition file for it.

I took My Guys example and changed it to use MDriven as backend instead of the mock test data store he had there.

Step 1

– Use MDriven Framework for the classes and ViewModels

image

ViewModels

imageimage

I know what you are thinking – what is the use of viewmodels if they are the same as the real model classes? Argument 1: they are not the same – the association from the real model is done as an Id in the ViewModel. Argument 2: this is just a sample and I bet you know they will be needed as more perspectives are added to the application.

This is what MDriven needs to generate the code for us.

Step 2

– Use ApiController to handle get and post

The complete code for the CategoriesController

using Eco.Linq;
using Eco.ViewModel.Runtime;
using EcoProject1;
using EcoProject1.EcoSpaceAndModel1;
using System.Linq;
using System.Web.Http;
using System.Web.Http.Controllers;

namespace OneStopTechVids.WebAPI
{
  public class CategoriesController : ApiController
  {

    private EcoProject1EcoSpace _es;
    protected override void Initialize(HttpControllerContext controllerContext)
    {
      base.Initialize(controllerContext);
      _es = new EcoProject1EcoSpace();
      _es.Active = true;
    }

    protected override void Dispose(bool disposing)
    {
      _es.AllowDeactivateDirty = true;
      _es.Dispose();
      _es = null;
      base.Dispose(true);
    }

    // GET api/<controller>
    public IHttpActionResult Get()
    {
      var categs = (from v in _es.PSQuery<Category>() select (v)).ToArray();
      return Ok(ViewModelHelper.CreateDynamicsFromViewModel("CategoryVM", _es, categs));
    }

  }
}

And some examples from the TechVideoController:

This is how I get all Videos:

// GET api/<controller>
        public IHttpActionResult Get()
        {
            var videos = (from v in _es.PSQuery<TechVideo>() select (v)).ToArray();
            return Ok(ViewModelHelper.CreateDynamicsFromViewModel("VideoVM", _es, videos));
        }

And this is now a new video gets created:

public IHttpActionResult Post([FromBody]VideoVM offlineVideo)
        {
          // Round about way to handle the Id assignment - did not want to change 
          // to much in the original sample - otherwise I would have used ExternalId
          var videos = ((from v in _es.PSQuery<TechVideo>() orderby(v.Id) descending 
select (v)).Take(1)).ToArray(); int maxId = 1; if (videos.Count()>0) maxId = videos[0].Id; offlineVideo.Id = maxId + 1; var theActualVideo = new TechVideo(_es); VideoVM newonline = VideoVM.Create(_es, theActualVideo); ViewModelHelper.ApplyValues(offlineVideo, newonline,null); // But we need to resolve the CategId and set the Category if (offlineVideo.Categoryid != -1) { // Categ is treated as id on client... look up to real object int categid = offlineVideo.Categoryid; var categs = ((from v in _es.PSQuery<Category>() where (v.Id==categid)
select (v)).Take(1)).ToArray(); if (categs.Count() > 0) theActualVideo.Category=categs[0]; } _es.UpdateDatabase(); return Ok(offlineVideo); }

And how it is updated:

public IHttpActionResult Put(int id, [FromBody]VideoVM offlineVideo)
        {
          var videos = ((from v in _es.PSQuery<TechVideo>() where (v.Id==id) 
select (v))).ToArray(); if (videos.Count() > 0) { var onlinevm=VideoVM.Create(_es, videos[0]); ViewModelHelper.ApplyValues(offlineVideo, onlinevm,null); // But the Categ is an Id... int categid = offlineVideo.Categoryid; var categs = (from v in _es.PSQuery<Category>() where (v.Id == categid)
select (v)).ToArray(); if (categs.Count() > 0) videos[0].Category = categs[0]; _es.UpdateDatabase(); return Ok(); } return NotFound(); }

Step 3

– Use Typescript on the client side:

When I use TypeScript I can create classes – I recreate my viewmodels

(I know – someone should totally write an MDriven Plugin to do this on each codegen!!!)

module Extensions {
    export class Video {
        id: number;
        title: string;
        description: string;
        author: string;
        rating: number;
        categoryid: number;
    }

    export class Category {
        id: number;
        name: string;
    }

Step 4

– Use AngularJS to create a scope and to handle to local store of objects:

export class TechVidsDataSvc {
        private videos: Array<Extensions.Video>;
        private categories: Array<Extensions.Category>;
        private techVidsApiPath: string;
        private categoriesApiPath: string;
        private httpService: ng.IHttpService;
        private qService: ng.IQService;

        getAllVideos(fetchFromService?: boolean): ng.IPromise<any> {
            var self = this;

            if (fetchFromService) {
                return getVideosFromService();
            } else {
                if (self.videos !== undefined) {
                    return self.qService.when(self.videos);
                } else {
                    return getVideosFromService();
                }
            }

            function getVideosFromService() : ng.IPromise<any> {
                var deferred = self.qService.defer();

                self.httpService.get(self.techVidsApiPath).then(
function
(result: any) { self.videos = result.data; deferred.resolve(self.videos); }, function (error) { deferred.reject(error); }); return deferred.promise; } }

This code kind of goes on for a bit. It does client side things like filtering the videos on category for example:

function filterVideos() {
                for (var counter = 0; counter < self.videos.length; counter++) {
                    if (self.videos[counter].categoryid === id) {
                        filteredVideos.push(self.videos[counter]);
                    }
                }

                return filteredVideos;
            }

Having client side data controlled by angularJS my views can databind and bind event handling like this:

<div class="col-md-4 rowmargin" ng-repeat="video in videos">
    <div class="thumbnail" title="{{video.description}}">
        <div class="caption"><strong>{{video.title}}</strong></div>
        <button class="right glyphicon glyphicon-chevron-up voting-button" 
                ng-disabled="video.rating == 5"
ng-click="upRate(video.id, video.rating)"></button> <div style="text-align:center;">by {{video.author}}</div> <span class="right" style="margin-right: 9px;">{{video.rating}}</span> <button class="right glyphicon glyphicon-chevron-down voting-button" ng-disabled="video.rating == 1"
ng-click="downRate(video.id, video.rating)"></button> <br /> <div><a href="#/edit/{{video.id}}">Edit / Delete</a></div> </div> </div>

I can of course use  the MDriven Debugger to add some test data:

image

 

 

image

And then run:

image

 

Summary

Microsoft tried Silverlight – but it never took of – so what should one use to get a rich client programming environment? I am pretty sure that JavaScript is not the final answer but it is the current answer.

It is possible that I will tinker along with some generic JavaScript helper that does more of the things that are possible with MDriven – but you can use these strategies today. You will not get any benefits on the client side but you will have all the benefits of MDriven on the Server side. And if you combine this with some automated UI in MVC5 or WPF you will free up a lot of time from doing standard stuff that can you can put into the the non standard stuff.

Updates 2014-08-03

As I continued to work on the Offline ViewModel class for different reasons I changed them so that Offline viewmodel classes has access to their meta data in form of the ViewModelClass and ViewModelColumn. As I had done this change to MDriven the serialization back from JavaScript just stopped to work. It did not throw any exceptions – it just plainly stopped to execute my handler methods.

After a lot of digging I found that first the JSon serializer got confused because it now found stuff in the VMClass.AsDictionary and it tried to serialize those things as well. We do not want that.

This is fixed by adding this code to your Global.asax.sc:

  public class ShouldSerializeContractResolver : CamelCasePropertyNamesContractResolver
  {
    public new static readonly ShouldSerializeContractResolver Instance = new ShouldSerializeContractResolver();
    protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
    {
      JsonProperty property = base.CreateProperty(member, memberSerialization);
      if ((property.DeclaringType.IsSubclassOf(typeof(VMClass)) && property.PropertyName == "AsDictionary"))  
// Do not serialize VMClass.AsDictionary { property.ShouldSerialize = instance => { return false; }; } return property; } }

And then use this new IContractResolver:

        protected void Application_Start(object sender, EventArgs e)
        {
            var formatters = GlobalConfiguration.Configuration.Formatters;
            var jsonFormatter = formatters.JsonFormatter;
            var settings = jsonFormatter.SerializerSettings;
            settings.Formatting = Formatting.Indented;
            settings.ContractResolver = ShouldSerializeContractResolver.Instance;

 

But it still would not play ball. It turned out that there is a thing called IBodyModelValidator. This sucker tries to somehow validate objects we use as arguments in wepapi handlers. I do not know why. It does a rather strange job. It inspects and follow all properties to the bitter end. Since our generated classes inherit from VMClass that has a few things like VIewModelClass etc that leads straight into the MDriven Framework – the IBodyModelValidator tried to validate the whole world. Again I cannot understand why.

We need to make it stop. Add this in your Global.asax.cs:

  public class CustomBodyModelValidator : DefaultBodyModelValidator
  {
    public override bool ShouldValidateType(Type type)
    {
      if (type.IsSubclassOf(typeof(VMClass)) || type == typeof(VMClass))
        return false;
      return  base.ShouldValidateType(type);
    }
  }

And then use it:

        protected void Application_Start(object sender, EventArgs e)
        {
          GlobalConfiguration.Configuration.Services.
               Replace(typeof(IBodyModelValidator), new CustomBodyModelValidator());

 

Then everything works as it should again

This entry was posted in AngularJS, Javascript, TypeScript, ViewModel and tagged . Bookmark the permalink.

8 Responses to AngularJS

  1. Admin says:

    You can download this code from here //www.capableobjects.com/xdownloads/angularjs-with-typescript-dncmag-12-master.rar

  2. guest00 says:

    CO,
    Thank you!!!

    (some ideas how to host ECO in node.js you can find here http://tjanczuk.github.io/edge/#/40)

  3. guest00 says:

    Hi Hans,

    Tried this example with and without workarounds – have “System.InvalidOperationException:
    Type ‘MyEcoWebAPI.EcoSpaceAndModel.ViewModelCodeGen_vmClass1.vmClass1’ with data contract name ‘vmClass1:http://schemas.datacontract.org/2004/07/MyEcoWebAPI.EcoSpaceAndModel.ViewModelCodeGen_vmClass1‘ is not expected. Consider using a DataContractResolver or add any types not known statically to the list of known types – for example, by using the KnownTypeAttribute attribute or by adding them to the list of known types passed to DataContractSerializer.”

    Could you please advise how to solve it?

    VS2013/WebAPI project/.Net 4.5/ECO 7.0.0.7875

    Thank you,
    Regards,
    Alexandr

  4. Admin says:

    Hi Alexandr,
    I suspect Json.newton type mismatch. Anyway we are way past this with MDriven Turnkey. I will happily provide an evaluation site so that you can run turnkey against our azure store – or you can set up an MDrivenServer and MDrivenTurnkey in your own environment.

    Turnkey stream changes to and from the angualar domain layer in the client. It is a superb way to build angular apps!

  5. guest00 says:

    Hi Hans,
    I found mistakes I made – I used web.api/.net 4.5 + EcoSpaceAndModel 7.0 project instead of suggested .net 4.0. – so ECO codegen generated different VM classes (properties/methods without _OfflineAware) and different Json.newton dlls of course.
    Thank you for your answer – reading books about angular and setting up local MDriven servers.

    Regards,
    Alexandr

  6. guest00 says:

    To avoid “System.InvalidOperationException” put the following 2 lines in to the begining of the Application_Start() in Global.asax to remove xml formatters.
    Json forrmatters for VMs work well.
    GlobalConfiguration.Configuration.Formatters.JsonFormatter.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore;
    GlobalConfiguration.Configuration.Formatters.Remove(GlobalConfiguration.Configuration.Formatters.XmlFormatter);

Leave a Reply

Your email address will not be published. Required fields are marked *

*