سؤال

I'm having some problems editing the properties of a related object. Here is the code:

Model:

DocumentLine.cs

/// <summary>
/// States the base implementation for all document lines in a purchasing module.
/// </summary>
public class DocumentLine : Keyed
{
    // ... some other properties

    /// <summary>
    /// Gets or sets the current line's document header.
    /// </summary>
    [Navigation]
    [Display(ResourceType = typeof(Resources.ApplicationResources), Name = "Header")]
    public virtual DocumentHeader Header { get; set; }
}

DocumentHeader.cs

/// <summary>
/// States the base implementation for all document headers in a purchasing module.
/// </summary>
public class DocumentHeader : Keyed
{
    /// <summary>
    /// Gets or sets the current header's document number.
    /// </summary>
    [Required]
    [Display(ResourceType = typeof(Resources.ApplicationResources), Name = "DocumentNumber")]
    public string DocumentNumber { get; set; }

    /// <summary>
    /// Gets or sets the extra cost of the document.
    /// </summary>
    [Display(ResourceType = typeof(Resources.ApplicationResources), Name = "ExtraCost")]
    [RegularExpression(@"^\d*$", ErrorMessageResourceType=typeof(Resources.ApplicationResources), ErrorMessageResourceName= "Exception_ExtraCost_Error")]
    public decimal ExtraCost { get; set; }

    /// <summary>
    /// Gets or sets the vat's extra cost of the document.
    /// </summary>
    [Display(ResourceType = typeof(Resources.ApplicationResources), Name = "ExtraVat")]
    [RegularExpression(@"^\d*$", ErrorMessageResourceType = typeof(Resources.ApplicationResources), ErrorMessageResourceName = "Exception_ExtraVat_Error")]
    public decimal ExtraVat { get; set; }

    /// <summary>
    /// Gets or sets the navigation property to all dependant Document lines.
    /// </summary>
    [Required]
    [Navigation]
    [Display(ResourceType = typeof(Resources.ApplicationResources), Name = "DocumentLines")]
    public virtual ICollection<DocumentLine> DocumentLines { get; set; }
}

View:

@Html.HiddenFor(model => model.Header.Id, Model.Header != null ? Model.Header.Id : null)
<div class="display-label">
    @Html.DisplayNameFor(model => model.Header.ExtraCost)
</div>
<div class="display-field">
    <input type="text" name="Header.ExtraCost" id="Header.ExtraCost" data-varname="header.extraCost" value="@(Model.Header.ExtraCost)" />
    @Html.ValidationMessageFor(model => model.Header.ExtraCost)
</div>
<div class="display-label">
    @Html.DisplayNameFor(model => model.Header.ExtraVat)
</div>
<div class="display-field">
    <input type="text" name="Header.ExtraVat" id="Header.ExtraVat" data-varname="header.extraVat" value="@(Model.Header.ExtraVat)" />
    @Html.ValidationMessageFor(model => model.Header.ExtraVat)
</div>

I am aware that MVC tracks the ids and names of the inputs to pass the values to the controller, that is why I put the hidden input for the Header.Id value. This view correctly shows the values so I do not think the problem is in here.

Controller:

I have a generic controller method to edit which works fine, although I may have to override it for this particular case.

/// <summary>
/// Handles the POST event for the Edit action, updating an existing TEntity object.
/// </summary>
/// <param name="id">Id of the TEntity object to update.</param>
/// <param name="model">TEntity object with properties updated.</param>
/// <returns>Redirection to the Index action if succeeded, the Edit View otherwise.</returns>
[HttpPost]
public virtual ActionResult Edit(string id, TEntity model)
{
    var request = new RestSharp.RestRequest(Resource + "?id={id}", RestSharp.Method.PUT) { RequestFormat = RestSharp.DataFormat.Json }
        .AddParameter("id", id, RestSharp.ParameterType.UrlSegment)
        .AddBody(model);
    var response = Client.Execute(request);

    // Handle response errors
    HandleResponseErrors(response);

    if (Errors.Length == 0)
        return RedirectToAction("Index");
    else
    {
        ViewBag.Errors = Errors;
        return View(model);
    }
}

The main problem is that this code is not only not editing the related object values, but also sets to null the Header.Id value of the DocumentLine.

Any advice?

هل كانت مفيدة؟

المحلول 2

I had to modify the default RestSharp PUT method to force it to update first the document header, then the invoice line.

/// PUT api/<controller>/5
/// <summary>
/// Upserts a InvoiceLine object and its DocumentHeader to the underlying DataContext
/// </summary>
/// <param name="id">Id of the InvoiceLine object.</param>
/// <param name="value">The InvoiceLine object to upsert.</param>
/// <returns>An HttpResponseMessage with HttpStatusCode.Ok if everything worked correctly. An exception otherwise.</returns>
public override HttpResponseMessage Put(string id, [FromBody]InvoiceLine value)
{
    //If creation date is in UTC format we must change it to local time
    value.DateCreated = value.DateCreated.ToLocalTime();

    //update the document header if there is any change
    var header = Database.Set<DocumentHeader>()
        .FirstOrDefault(x => x.Id == value.Header.Id);

    if (header != null)
    {
        value.Header.DocumentLines = header.DocumentLines;
        value.Header.DocumentNumber = header.DocumentNumber;
        Database.Entry<DocumentHeader>(header)
            .CurrentValues.SetValues(value.Header);
    }
    else
    {
        Database.Set<DocumentHeader>().Add(value.Header);
    }

    // If entity exists, set current values to atomic properties
    // Otherwise, insert as new
    var entity = Database.Set<InvoiceLine>()
        .FirstOrDefault(x => x.Id == id);

    if (entity != null)
    {
        Database.Entry<InvoiceLine>(entity)
            .CurrentValues.SetValues(value);
        FixNavigationProperties(ref entity, value);
    }
    else
    {
        FixNavigationProperties(ref value);
        Database.Set<InvoiceLine>().Add(value);
    }

    if (value is ISynchronizable)
        (value as ISynchronizable).LastUpdated = DateTime.UtcNow;

    // Save changes and handle errors
    SaveChanges();

    return new HttpResponseMessage(HttpStatusCode.OK);
}

This worked for me. Hope that helps.

نصائح أخرى

The problem is more than likely in the last paragraph of my answer here, but here are some other tips to help you debug issues like this..

Look at the network tab in Google chrome, or download Firebug for Firefox and look at what you are actually posting to the method, put a break point on the method and make sure that the parameters of the method are actually getting values.

Remove the "Header." from the name and id of your inputs, actually use @Html.EditorFor(model => model.ExtraCost) instead. You haven't posted your GET method for the Edit view, breakpoint this and make sure an entity is being passed to the view.

If you get this working you only need to use @Html.HiddenFor(model => model.Id)

In your view the Id will be posted as Id, put in your controller it is called id, thsi will not bind, so I would suspect the Id is never actually getting past to the ActionResult.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top