2

I'm building a simple CMS for a website. The site is built with asp.net mvc and I'm using knockout.js for two-way binding. I can send my values from DB to my view and change the model values. My problem comes when I try to save the modified viewmodel. I'm saving the data in DB using the following class:

public class Content
{
    public int ContentId { get; set; }
    public string Name { get; set; }
    public string Value { get; set; }
}

My full viewmodel:

public class CmsStartPageViewModel
{
    public string SectionOneTitle {get; set;}
    public string SectionOneText {get; set;}
    public string SectionTwoTitle {get; set;}
    public string SectionTwoText {get; set;}
    public string SectionThreeTitle {get; set;}
    public string SectionFourTitle {get; set;}
    public string BannerOneTitle {get; set;}
    public string BannerOneSubTitle {get; set;}
    public string BannerOneIcon {get; set;}
    public string SectionFiveTitle {get; set;}
    public string SectionFiveSubTitle {get; set;}
    public string ContactTitle {get; set;}
    public string ContactSubTitleOne {get; set;}
    public string ContactText {get; set;}
    public string ContactSubTitleDetails {get; set;}
    public string ContactSubTitleSocial {get; set;}

    public List<SupportingCompany> SupportingCompanies { get; set; }
    public List<ReasonTemplate> ReasonTemplates { get; set; }
    public List<ProductInfoTemplate> ProductInfoTemplates { get; set; }

    //Kontaktformulär Validering

    public string FormFullName { get; set; }
    public string FormEmail { get; set; }
    public string FormSubject { get; set; }
    public string FormMessage { get; set; }


    public void PopulateViewModel(ICollection<StartContent> data)
    {
        var self = this;

        var properties = typeof(CmsStartPageViewModel).GetProperties();

        foreach (PropertyInfo property in properties)
        {
            var type = property.PropertyType;
            if (type == typeof(String) &! property.Name.StartsWith("Form"))
            {
                var value = data.Single(d => d.Name == property.Name);
                property.SetValue(self, value.Value);
            }
        }
    }
}

My save-function (knockout.js viewmodel):

ViewModel = function(data) {
    var self = this;
    ko.mapping.fromJS(data, {}, self);

    self.save = function () {
        var model = ko.mapping.toJSON(self);
        $.ajax({
            url: "/Cms/SaveViewModel",
            type: "POST",
            data: model,
            dataType: "json",
            contentType: "json",
            success: function(message) {
                ko.mapping.fromJS(data.viewModel, {}, self);
                if (message.Status === "success") {
                    toastr.success(message.Content);
                } else if (message.Status === "error") {
                    toastr.error(message.Content);
                }
            }
        });
    }
}

And finally, my Save action method:

    [HttpPost]
    public ActionResult SaveViewModel(CmsPageViewModel model)
    {
        var content = _contentRepo.GetStartContent();

        foreach (var item in content)
        {
            var property = model.GetType().GetProperty(item.Name);
            property.SetValue(model, item.Value);
        }

        _contentRepo.UpdateStartContent(content);

        var message = new StatusMessageCms
        {
            Content = "Your changes has now been saved!",
            Status = "success"
        };

        return Json(message);
    }

Update:

The JSON-model that's getting sent to server. I can't find any mismatches with my C# Viewmodel

    {
    "SectionOneTitle": "Aja-Baja.se avskräcker stöld och bedrägerier",
    "SectionOneText": "När vi blir tillräckligt många, kan vi stöldförsäkra till ett subventionerat pris.<br />\nIntresse från flera försäkringsbolag finns",
    "SectionTwoTitle": "Om Aja-Baja.se",
    "SectionTwoText": "Vi är två crossmotionärer som ser ett växande problem med stölder av motorcrosscyklar. För att förhindra detta så startar vi ett register där alla kan registrera sina crossar. <br /><br />\n\nDU vill inte köpa en stulen cross och inte heller bli bestulen på dem. Om alla som har en cross eller tänker köpa en, använder registret och sökfunktionen så hjälps vi åt att hålla koll så att det inte blir så intressant att stjäla dem.",
    "SectionThreeTitle": "Varför ska du registrera dig?",
    "SectionFourTitle": "Priser & Specifikationer",
    "BannerOneTitle": "Samarbeta mot inbrottstjuvarna",
    "BannerOneSubTitle": "REGISTRERA DIG HOS AJA-BAJA OCH HÅLL KOLL DU MED!",
    "BannerOneIcon": "fa-users",
    "SectionFiveTitle": "Vi är inte de enda som är entusiastiska inför Aja-Baja.se...",
    "SectionFiveSubTitle": "Flera samarbetspartners tror på vår idé",
    "ContactTitle": "Kontakta Oss",
    "ContactSubTitleOne": "Gör oss <strong>Bättre</strong>",
    "ContactText": "Det är viktigt för oss och veta vad ni som besökare tycker om vår Mobilapp och Webbapplikation. Har ni ett förslag på vad som skulle kunna göras bättre? Eller är det något som ni saknar? Fyll isåfall i formuläret och skicka det till oss. Tillsammans är vi starka!",
    "ContactSubTitleDetails": "Aja-Bajas <strong>Kontaktuppgifter</strong>",
    "ContactSubTitleSocial": "Sociala <strong>Medier</strong>",
    "SupportingCompanies": [{
        "SupportingCompanyId": 1,
        "ImageUrl": "/Content/img/sponsor/alternativ_mc_sponsor.png",
        "LinkUrl": "http://www.alternativ1mc.se/"
    }, {
        "SupportingCompanyId": 2,
        "ImageUrl": "/Content/img/sponsor/eliasson_racing.png",
        "LinkUrl": "http://www.eliassonracing.se/"
    }, {
        "SupportingCompanyId": 3,
        "ImageUrl": "/Content/img/sponsor/KonicaMinolta_Logo.png",
        "LinkUrl": "http://www.konicaminolta.se/sv/home.html"
    }, {
        "SupportingCompanyId": 4,
        "ImageUrl": "/Content/img/sponsor/frisk_sponsor.png",
        "LinkUrl": null
    }],
    "ReasonTemplates": [{
        "ReasonTemplateId": 1,
        "ReasonTitle": "Sökbarhet",
        "ReasonText": "Möjlighet att via ramnummer se om crossen är stulen.",
        "ReasonIcon": "fa-search"
    }, {
        "ReasonTemplateId": 2,
        "ReasonTitle": "Stöldanmälning",
        "ReasonText": "Anmäl snabbt din cross stulen via app eller hemsida.",
        "ReasonIcon": "fa-bullhorn"
    }, {
        "ReasonTemplateId": 3,
        "ReasonTitle": "Samarbetspartners",
        "ReasonText": "Verkstäder och återförsäljare kan kontrollera om crossen är stulen.",
        "ReasonIcon": "fa-users"
    }, {
        "ReasonTemplateId": 4,
        "ReasonTitle": "Ägarbyten",
        "ReasonText": "Ett smidigt och säkert sätt att registrera ett ägarbyte.",
        "ReasonIcon": "fa-refresh"
    }, {
        "ReasonTemplateId": 6,
        "ReasonTitle": "Motverka stölder",
        "ReasonText": "Stöldanmälda registrerade crossar blir svårare att sälja vidare.",
        "ReasonIcon": "fa-lock"
    }, {
        "ReasonTemplateId": 8,
        "ReasonTitle": "Förmåner som kund",
        "ReasonText": "Rabatter hos utvalda verkstäder och butiker.",
        "ReasonIcon": "fa-user"
    }],
    "ProductInfoTemplates": [],
    "FormFullName": null,
    "FormEmail": null,
    "FormSubject": null,
    "FormMessage": null
}

Edited question

The issue I encounter is that when I try to post to the controller all values are null. The ajax hits the correct Action and I can't find any differences between my posted JSON-model and my C# viewmodel.

Any help is appreciated!

Martin Johansson

6
  • If your code above is correct, then your ViewModel in fact does not have a parameterless constructor: public CmsStartPageViewModel(ICollection<StartContent> data) Commented Dec 9, 2015 at 13:25
  • I know, I can add it, but as I said, all values then become null in controller. Commented Dec 9, 2015 at 13:26
  • Can you show us how you initialise the ViewModel? Commented Dec 9, 2015 at 13:30
  • When I Post I don't initialize it, ko.mapping.toJSON() handles the conversion of viewmodel to json and asp.net tries to convert that to C# viewmodel automatically. Not sure how that's done, but I've had it working with another viewmodel. The difference is that I need a constructor for this one. Commented Dec 9, 2015 at 13:35
  • Where do you see this error? Is it from knockout side or from the C# side? Commented Dec 9, 2015 at 13:40

3 Answers 3

3

First: Your ContentType is not complete!

Second: You map your data to self, which is correct, then you assign the method save() to self and send it via post as data, so you are also posting save().

Try this:

ViewModel = function(data) {
    var self = this;
    var dataToPost = ko.mapping.fromJS(data, {}, self); 

    /* 
       You can pick another name for the variable dataToPost 
       This has to be a valid json, use a debugger or fiddler to see how it looks like
    */

    self.save = function () {
        var model = ko.mapping.toJSON(dataToPost);
            $.ajax({
            url: "/Cms/SaveViewModel",
            type: "POST",
            data: model,
            dataType: "json",
            contentType: "application/json; charset=utf-8",
            success: function(message) {

                ko.mapping.fromJS(data.viewModel, {}, self);

                if (message.Status === "success") {
                    toastr.success(message.Content);
                } else if (message.Status === "error") {
                    toastr.error(message.Content);
                }

            }
        });
    }
}

Hope it helps!

Sign up to request clarification or add additional context in comments.

1 Comment

contentType: "application/json; charset=utf-8", and ko.mapping.toJSON(..) were the missing link for me.
1

Your view model has a bit of logic in it. I personally do not recommend it. If you are using the constructor to set values this way, you are essentially limiting your ability to have things like collections etc as properties. So let's put that problem aside and figure out a solution.

  1. Do not use constructor of a view model to populate individual attributes. If you must populate values this way, use a separate public method.
  2. Now you have an empty constructor. After initialising make sure to call the public method to populate initial data.
  3. Now when you post, IF your knockout viewModel has matching attributes to your C# viewModel, you should receive a properly populated instance of your view model in SaveViewModel method.

1 Comment

Yeah, I should not have used a constructor for that operation, much better with a method. But I still get null values on everything. I'll update the question with my Json model that's getting sent to server and my complete viewmodel.
0

There is no way to omit constructor with parameters when you want to create instance of a class (apart of using static prototype and cloning object with MemberwiseClone, but in your solution it would require building custom model binder and parse all data manually).

If you have added parameterless constructor and after posting model properties are empty, that means that binder wasn't ably to properly bind data from request to your model. You should double check what actually goes from browser to server. Try to change your ajax request to (the JSON.stringify part is important)

contentType: 'application/json; charset=utf-8', data: JSON.stringify(model)

1 Comment

Moved constructor to method and added JSON-model that's being sent from server and my full C# viewmodel to my question. I think that ko.mapping.ToJSON() function converts the model to json so I should not have to stringify it again.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.