Search code examples
jqueryasp.net-mvcarchitectureknockout.jsknockout-mapping-plugin

Large Scale MVC Web Application - Using Knockout and Razor


I am working on a large scale web application and I am having issues with properly architecting it using knockout and MVC.

I currently have a base view model that I pass into all of my views that contains basic data regarding the currently logged in user

//Example Code
public class BaseViewModel {
    public String FullName;
    public String Email;

    //A bunch of other similar values
}

I then have view models that are specific to each page on the site (ie. Profile) that inherit the BaseViewModel class

//profile.cshtml
public class UserProfileViewModel : BaseViewModel {
    public String Address;
    public String PhoneNumber;

    //Other similar values
}

//entity.cshtml
public class UserEntityViewModel : BaseViewModel {
    public String EntityValue;
    public Int32 EntityNumber;

    //Other similar values
}

I have redefined my entire data model in javascript using knockout observables so that I can create objects of any type that I have in my MVC model. I then have several viewmodels defined in javascript that are basically the same as my MVC view models to allow load, create, edit, delete functionality.

I find that this works great in instances where I would like to create a new entity from the profile page for example. I can create a new instance of the viewmodel and bind it to a new jquery dialog, when the OK button is clicked I can call an event in my knockout viewmodel that will save or create the entity.

This architecture does not work so great when I want to load the data into the specific page (ie. I would like to populate the profile page using my knockout data model). I run into issues where I need to determine which page that I am on and bind a specific viewmodel to that page. I really don't think that the following code is very elegant.

$(function() {
    if ($('.tweak-list').length) {
        var id = $('.tweak-list').attr('data-songid');
        var vm = new my.tweaksviewmodel({ songid: id });
        ko.applyBindings(vm);
    }
});

Does anyone have any advice as to how I should architect this? I am thinking that it would be best to create a BaseViewModel in javascript and use the knockout mapping plugin http://knockoutjs.com/documentation/plugins-mapping.html to automatically create my various data models. That said, it doesn't really solve the issue of determining which page that the model has been bound from so that it can load the appropriate data.

Edit 1

This architecture also has the limitation that I cannot take advantage of using modal popups to add data as I need to re-bind the viewmodel to each modal..

Anyone have any ideas?


Solution

  • I would suggest to architect it following way:

    Create a different JavaScript module with specific KnockOut ViewModel for every page:

    var app = app || {};
    app.pages = app.pages || {};
    
    app.pages.userProfile = (function () {
    
        function UserProfileViewModel() {
            //view model specific code
        };
    
        function init() {
            ko.applyBindings(new UserProfileViewModel());
        };
    
        return {
            init: init
        };
    }());
    

    Specify which JS module should be used in a razor view:

    @model dynamic
    
    @{
        ViewBag.CurrentPageJsHandler = "app.pages.userProfile";
        Layout = "~/Views/Shared/_Layout.cshtml";
    }
    
    <p>Page specific content</p>
    

    Add page specific module initialization code to layout file:

    <html>
    <body>   
        <div id="content">
            @RenderBody()
        </div>      
        @if (@ViewBag.CurrentPageJsHandler != null)
        {
            <script>
                $(function() {
                    app.currentPage = @ViewBag.CurrentPageJsHandler;
                    app.currentPage.init();
                });
            </script>
        }    
    </body>
    </html>
    

    This way you can encapsulate all page related code in different modules. Since each JS module has unique name you can bindle all of them together and include in layout.