Base solution for your next web application
Open Closed

Saving data using Nested DTOs issue #1251


User avatar
0
softbasepd created

Hi,

I'm surprised that nobody has discussed about this on the forum and I have searched to find some sort of solution to this but with no luck.

We're having issues trying to save data from the front end to the entity. First of all, we're dealing with an entity where related entities are in an entity. We are using Automapper to map the Dtos with the entities. We created the Dtos for each entity and it works fine when you populate the form but when you try to save the data back via the WebAPI which takes in a JSON object, it shows NULL for the related Dtos.

Below is an example of our code.

Please let us know how to resolve it the best way possible.

We already know that if we flatten the entities into one Dto it will work along with automapping configuration but we want to do it without all of this because it'll be a headache to create automapping configuration for everything.

Thanks, Dean

CSHTML File:
@using Abp.Web.Mvc.Extensions
@using Web.Areas.Mpa.Models.Common.Modals
@model Web.Areas.Mpa.Models.Vendors.CreateOrEditVendorModalViewModel

@section Scripts
{
    @Html.IncludeScript("~/Metronic/Assets/Frontend/Pages/Scripts/form-validation.js")

}

@{ var title = string.Format("{0} {1}", L("Manage"), L("Vendor")); }
@Html.Partial("~/Areas/Mpa/Views/Common/Modals/_ModalHeader.cshtml", new ModalHeaderViewModel(title))


<div class="modal-body container-fluid">
    
    <form role="form" novalidate class="form-validation">
        <div class="form-body">
            <div id="ValidationSummary" style="display: none" class="alert alert-warning">
                <label class="control-label"><b>@L("ValidationSummary")</b></label>
                <ul id="ValidationErrors"></ul>
            </div>
            <div style="display: none">
                @(Html.Hidden("Id", Model.Vendor.Id))
            </div>
            <div class="form-group col-xs-12">
                <div class="input-group">
                    <label class="control-label">@L("Name")</label>
                    <span class="required"> * </span>
                    <div>
                        @{ string vendorName = string.Format("{0} {1}", @L("Vendor"), @L("Name")); }
                        @(Html.Kendo().TextBox()
                              .Name("Name")
                              .HtmlAttributes(new { required = "required", placeholder = vendorName, style = "width:800px" })
                              .Value(@Model.Vendor.Name ?? string.Empty))
                    </div>
                    <div>
                        <span class="k-invalid-msg" data-for="Name"></span>
                    </div>
                </div>
            </div>
            <div class="form-group col-xs-12" style="padding-top: 20px;">
                <h4 style="padding-bottom: 3px; border-bottom-width: 1px; border-bottom-style: solid; width: 800px;">@L("Address")</h4>
                @*<hr />*@
            </div>
            <div class="form-group col-xs-12">
                <div class="input-group ">
                    <label>@L("Street1")</label>
                    <span class="required"> * </span>
                    <div>
                        @(Html.Kendo().TextBox()
                          .Name("AddressStreet1")
                          .HtmlAttributes(new { required = "required", placeholder = @L("Street1"), style = "width:800px" })
                          .Value(@Model.Vendor.Address.Street1 ?? string.Empty))
                    </div>
                </div>
                <div>
                    <span class="k-invalid-msg" data-for="Street1"></span>
                </div>
            </div>

            <div class="form-group col-xs-12">
                <div class="input-group">
                    <label>@L("Street2")</label>
                    <div>
                        @(Html.Kendo().TextBox()
                          .Name("AddressStreet2")
                          .HtmlAttributes(new { placeholder = @L("Street2"), style = "width:800px" })
                          .Value(@Model.Vendor.Address.Street2 ?? string.Empty))
                    </div>
                </div>

            </div>
            <div class="form-group col-xs-12">
                <div class="input-group">
                    <label>@L("Street3")</label>
                    <div>
                        @(Html.Kendo().TextBox()
                          .Name("AddressStreet3")
                          .HtmlAttributes(new { placeholder = @L("Street3"), style = "width:800px" })
                          .Value(@Model.Vendor.Address.Street3 ?? string.Empty))
                    </div>
                </div>
            </div>
            <div class="form-group col-xs-12">
                <div class="input-group">
                    <label>@L("Street4")</label>
                    <div>
                        @(Html.Kendo().TextBox()
                          .Name("AddressStreet4")
                          .HtmlAttributes(new { placeholder = @L("Street4"), style = "width:800px" })
                          .Value(@Model.Vendor.Address.Street4 ?? string.Empty))
                    </div>
                </div>
            </div>
            <div class="form-group ">
                <div class="col-xs-6">
                    <div class="input-group" >
                        <label>@L("City")</label>
                        <span class="required"> * </span>
                        <div>
                            @(Html.Kendo().TextBox()
                              .Name("AddressCity")
                              .HtmlAttributes(new { required = "required", placeholder = @L("City"), style = "width:365px;" })
                              .Value(@Model.Vendor.Address.City ?? string.Empty))
                        </div>
                    </div>
                    <div>
                        <span class="k-invalid-msg" data-for="City"></span>
                    </div>
                </div>
                <div class="col-xs-6">
                    <div class="input-group" >
                        <label>@L("Province")</label>
                        <span class="required"> * </span>
                        @(Html.Kendo().TextBox()
                          .Name("AddressState")
                          .HtmlAttributes(new { required = "required", placeholder = @L("Province"), style = "width:365px" })
                          .Value(@Model.Vendor.Address.State ?? string.Empty))
                    </div>
                    <div>
                        <span class="k-invalid-msg" data-for="Province"></span>
                    </div>
                </div>
                <div style="clear: both;">
                </div>
            </div>
            <div class="form-group">
                <div class="col-xs-6">
                    <label>@L("Country")</label>
                    <div>
                        @(Html.Kendo().DropDownList()
                        .Name("AddressCountryId")
                        .DataTextField("Text")
                        .DataValueField("Value")
                        .BindTo(Model.CountrySelectListItems)
                        .HtmlAttributes(new { style = "width:365px;" })
                        .Value(Model.Vendor.AddressCountryId.ToString()))
                    </div>
                </div>
                <div class="col-xs-6">
                    <div class="input-group" style="width: 130px;">
                        <label>@L("PostalCode")</label>
                        <span class="required"> * </span>
                        @(Html.Kendo().TextBox()
                          .Name("AddressPostalCode")
                          .HtmlAttributes(new { required = "required", placeholder = @L("PostalCode"), style = "width:365px;" })
                          .Value(@Model.Vendor.Address.PostalCode ?? string.Empty))
                    </div>
                    <div>
                        <span class="k-invalid-msg" data-for="PostalCode"></span>
                    </div>
                </div>
                @*<div class="col-xs-4">
                </div>*@
            </div>
            <div class="form-group col-xs-12" style="padding-top: 20px;">
                <h4 style="padding-bottom: 3px; border-bottom-width: 1px; border-bottom-style: solid; width: 800px;">@L("PhoneNumber")</h4>
               
            </div>
            <div class="form-group">
                <div class="col-xs-6">

                    <div class="input-group" style="float: left; width: 200px; padding-right: 5px;">
                        <label>@L("DayPhone")</label>
                        <span class="required"> * </span>
                        @(Html.Kendo().TextBox()
                          .Name("DayPhoneNumber")
                          .HtmlAttributes(new { required = "required", placeholder = @L("DayPhone"), style = "width:270px;" })
                          .Value(@Model.Vendor.DayPhone.Number ?? string.Empty))
                    </div>
                    <div>
                        <span class="k-invalid-msg" data-for="DayPhoneNumber"></span>
                    </div>


                    <div style="float: left; width: 80px;">
                        <label>@L("Extension")</label>
                        @(Html.Kendo().TextBox()
                              .Name("DayPhoneExt")
                              .HtmlAttributes(new { placeholder = @L("Extension"), style="width:90px" })
                              .Value(@Model.Vendor.DayPhone.Ext ?? string.Empty))

                    </div>
                    <div style="clear: left;"></div>
                </div>


                @*<div class="form-group">*@
                <div class="col-xs-6">
                    <div class="input-group" >
                        <label>@L("SecondPhone")</label>
                        @(Html.Kendo().TextBox()
                                .Name("SecondPhoneNumber")
                                .HtmlAttributes(new { placeholder = @L("SecondPhone"), style = "width:365px;" })
                                .Value(@Model.Vendor.SecondPhone.Number ?? string.Empty))
                    </div>
                </div>
                @* </div>*@
            </div>


            <div class="form-group col-xs-12" style="padding-top: 20px;">
                <h4 style="padding-bottom: 3px; border-bottom-width: 1px; border-bottom-style: solid; width: 800px;">@L("BusinessNumber")</h4>
              
            </div>
            <div class="form-group col-xs-12">
                <div class="input-group" style="width: 240px;">
                    <label>@L("HstNumber")</label>
                    @(Html.Kendo().TextBox()
                          .Name("HstNumber")
                          .HtmlAttributes(new { required = "required", placeholder = @L("HstNumber"), style = "width:800px;" })
                          .Value(@Model.Vendor.HstNumber ?? string.Empty))
                </div>
            </div>
        </div>
    </form>
    
</div>

@Html.Partial("~/Areas/Mpa/Views/Common/Modals/_ModalFooterWithSaveAndCancel.cshtml")

================================================================================================================================================
JS File:
	(function ($) {
    app.modals.ManageModal = function () {
        var _modalManager;
        var _vendorService = abp.services.app.vendor;
        var _$form = null;
        var validator;

        this.init = function (modalManager) {
            _modalManager = modalManager;
             $('#' + _modalManager.getModalId()).find(".modal-content").css({ "width": "900px", "margin-left": "-10px" });
            _$form = _modalManager.getModal().find('form');
            validator = _$form.kendoValidator().data("kendoValidator");
        };

        this.save = function () {
            if (!validator.validate()) {
                showErrors(validator.errors());
                return;
            }
          
            var vendor = _$form.serializeFormToObject();
            debugger;
            _modalManager.setBusy(true);
            _vendorService.createOrUpdateVendor({
                vendor : vendor
            }).done(function () {
                _modalManager.close();
                location.reload();
            }).always(function () {
                _modalManager.setBusy(false);
            });
        };
    }; 
})(jQuery);
	
===============================================================================================================================================
CODE and DTOs:
public async Task CreateOrUpdateVendor(VendorCreateOrEditInput input)
    {
        if (input.Vendor.Id == 0)
            {
                var vendor = new Vendor();
                input.Vendor.MapTo(vendor);
                await _vendorRepository.InsertAsync(vendor);
            }
        else
            {
                var vendor = _vendorRepository.Get(input.Vendor.Id);

                input.Vendor.MapTo(vendor);

                await _vendorRepository.UpdateAsync(vendor);
            }
    }
		
		
 public class VendorCreateOrEditInput : IInputDto
    {
        public VendorDto Vendor { get; set; }
    }

[AutoMap(typeof(Vendor))]
public class VendorDto : EntityDto
    {
        public int Id { get; set; }

        public string Name { get; set; }

        public AddressDto Address { get; set; }
       
        public int AddressCountryId { get; set; }

        public PhoneDto DayPhone { get; set; }
     
        public PhoneDto SecondPhone { get; set; }

        public string HstNumber { get; set; }
    }


 [AutoMap(typeof(Address))]
 public class AddressDto : EntityDto
    {
        [Required]
        public string Street1 { get; set; }

        public string Street2 { get; set; }

        public string Street3 { get; set; }

        public string Street4 { get; set; }

        [Required]
        public string City { get; set; }
        public string State { get; set; }
        public string PostalCode { get; set; }     
        
        [NotMapped]
        public string UniqueId { get; set; }
      
        [Required]
        public CountryDto CountryDto { get; set; }
    }

 [AutoMapFrom(typeof(Country))]
 public class CountryDto : EntityDto
    {
        [Required]
        public int Id { get; set; }
 
		[Required]
        public string Name { get; set; }
    }
	
 [AutoMap(typeof(PhoneNumber))]
 public class PhoneDto : IDto
    {
        public string Number { get; set; }
        public string Ext { get; set; }
        public PhoneType PhoneType { get; set; }
    }
	
 public enum PhoneType
    {
        WorkPhone,
        HomePhone,
        DayPhone,
        Fax,
        Mobile
    }
	
// The following was used for flattened Dto
		Mapper.CreateMap<VendorDto, Vendor>()
			.ForMember(dest => dest.Address,
				opts => opts.MapFrom(
				src => new Address
                    {
                        Street1 = src.AddressStreet1,
                        Street2 = src.AddressStreet2,
                        Street3 = src.AddressStreet3,
                        Street4 = src.AddressStreet4,
                        City = src.AddressCity,
                        State = src.AddressState,
                        PostalCode = src.AddressPostalCode,
                        Country = _countryRepository.Get(src.AddressCountryId)
                    }));

2 Answer(s)
  • User Avatar
    0
    ismcagdas created
    Support Team

    Hi,

    For matching your html form to your dto you can use third party javascript libraries. One might be this <a class="postlink" href="http://maxatwork.github.io/form2js/">http://maxatwork.github.io/form2js/</a>.

    Entity Framework does not support saving nested entities. You can do it manually or there are also custom solutions for this like GraphDiff <a class="postlink" href="https://github.com/refactorthis/GraphDiff">https://github.com/refactorthis/GraphDiff</a>.

    I haven't tried GraphDiff yet but you can give it a try.

  • User Avatar
    0
    hikalkan created
    Support Team

    For GraphDiff, check also #1260