0

I'm certain this is a total noob issue. I am currently dealing with two main entities:

Request - A request to be sent to the purchasing department detailing information relevant to a purchase request.

namespace Purchasing.Models
{
public class Request
{
    public long ID { get; set; }

    [DisplayName("Total")]
    [Required]
    public decimal total { get; set; }

    [DisplayName("Date Created")]
    public DateTime dateCreated { get; set; }

    [DisplayName("Date Updated")]
    public DateTime dateModified { get; set; }

    [DisplayName("Reason for Purchase")]
    [Required]
    public string justification { get; set; }

    [Required]
    //[ForeignKey("User_ID")]
    public string userID { get; set; }

    [DisplayName("Current Status")]
    public Enums.RequestStatus status { get; set; }

    [DisplayName("Items")]
    [Required]
    public virtual ICollection<RequestLine> RequestLines { get; set; }

    [DisplayName("Budget")]
    [Required]
    public long budgetID { get; set; }
}

public class RequestDbContext : DbContext
{
    public RequestDbContext() : base("RequestDbContext")
    {
    }

    public static RequestDbContext Create()
    {
        return new RequestDbContext();
    }

    public DbSet<Request> Requests { get; set; }

    public System.Data.Entity.DbSet<RequestLine> RequestLines { get; set; }

    public System.Data.Entity.DbSet<Budget> Budgets { get; set; }
}
}

RequestLine - Line item details for a single item to be included in a purchasing request.

namespace Purchasing.Models
{
    public class RequestLine
    {
        public long ID { get; set; }

        [DisplayName("Description")]
        [Required]
        public string description { get; set; }

        [DisplayName("Quantity")]
        [Required]
        public decimal quantity { get; set; }

        [DisplayName("Price")]
        [Required]
        public decimal price { get; set; }

        [DisplayName("Amount")]
        public decimal amount { get; set; }

        public long requestID { get; set; }
        [Required]
        [ForeignKey("ID")]
        public Request Request { get; set; }
    }
}

What I am trying to accomplish is a view where RequestLines can be added to a Request before the Request is posted.

RequestController:

// GET: Request/Create
        public ActionResult Create()
        {
            string userID = User.Identity.GetUserId();
            ApplicationUser currentUser = userDb.Users.FirstOrDefault(x => x.Id == userID);

            if(currentUser != null)
            {
                ViewData["UserFullName"] = currentUser.FirstName + " " + currentUser.LastName;
                ViewData["UserDepartment"] = currentUser.Department.ToString();
            }

            Request request = new Request();
            request.dateCreated = DateTime.Now;
            request.RequestLines = new List<RequestLine>();

            request.userID = currentUser.Id.ToString();

            return View(request);
        }

// POST: Request/Create
        [HttpPost]
        [ValidateAntiForgeryToken]
        public ActionResult Create([Bind(Include = "ID,total,dateCreated,dateModified,justification,userID,status,RequestLines")] Request request)
        {
            if (ModelState.IsValid)
            {
                request.status = Enums.RequestStatus.New;

                foreach (RequestLine reqLine in request.RequestLines)
                {

                    request.total += reqLine.amount;
                    reqLine.requestID = request.ID;
                    db.RequestLines.Add(reqLine);
                }

                db.Requests.Add(request);
                db.SaveChanges();

                return RedirectToAction("Index");
            }

            return View(request);
        }

Ultimately what I'm trying achieve is a view for creating requests that will allow a user to create RequestLines for the Request and have them displayed in the create view before posting the overall Request.

Request/Create.cshtml (The table is displayed now by Angular, but no back-end data binding is performed.

@model Purchasing.Models.Request
@using Purchasing.Models

@{
ViewBag.Title = "Create";
    Layout = "~/Views/Shared/_Layout.cshtml";
}

<h2>Create</h2>


@using (Html.BeginForm()) 
{
@Html.AntiForgeryToken()

<div class="form-horizontal" ng-controller="reqLineCtrl">
    <h4>Request</h4>
    <hr />
    @Html.ValidationSummary(true, "", new { @class = "text-danger" })

    <div class="form-group">
        <div class="col-md-2">
            <b>@ViewData["UserFullName"]</b>
        </div>
        <div class="col-md-2">
            <b>@ViewData["UserDepartment"]</b>
        </div>
        <div class="col-md-2">
        </div>
        <div class="col-md-2">
            <b>@Model.dateCreated.ToString("MM/dd/yyyy")</b>
        </div>
    </div>
    <hr/>

    <div class="form-group">
        <div class="col-md-2">
            <label class="control-label" for="items">Items</label>
        </div>
        <div class="col-md-10">
            <table class="table" id="items">
                <caption><b>Request Total: ${{total | currency}}</b></caption>
                <tr>
                    <th>Description</th>
                    <th>Quantity</th>
                    <th>Price</th>
                    <th>Total</th>
                </tr>
                <tr ng-repeat="reqLine in lines">
                    <td>{{reqLine.description}}</td>
                    <td>{{reqLine.quantity}}</td>
                    <td>{{reqLine.price | currency}}</td>
                    <td>{{reqLine.price * reqLine.quantity | currency}}</td>
                </tr>
            </table>
        </div>
    </div>

    <div class="form-inline">
        <div class="form-group col-md-2">
            <label class="control-label col-md-6" for="quantity">Quantity</label>
            <div class="col-md-6">
                <input type="text" id="quantity" class="form-control" ng-model="line.quantity">
            </div>
        </div>

        <div class="form-group col-md-5">
            <label class="control-label col-md-2" for="description">Description</label>
            <div class="col-md-10">
                <input type="text" id="description" class="form-control" ng-model="line.description">
            </div>
        </div>

        <div class="form-group col-md-2">
            <label class="control-label col-md-6" for="price">Price</label>
            <div class="col-md-6">
                <input type="text" id="price" class="form-control" ng-model="line.price">
            </div>
        </div>

        <div class="form-group">
            <div class="col-md-offset-2 col-md-10">
                <input type="button" value="Add Item" class="btn btn-primary" ng-click="enterLine(line)"/>
            </div>
        </div>
    </div>
    <br/>
    <hr/>


    <div class="form-group">
        @Html.LabelFor(model => model.justification, htmlAttributes: new { @class = "control-label col-md-2" })
        <div class="col-md-10">
            @Html.EditorFor(model => model.justification, new { htmlAttributes = new { @class = "form-control" } })
            @Html.ValidationMessageFor(model => model.justification, "", new { @class = "text-danger" })
        </div>
    </div>

    <div class="form-group">
        <div class="col-md-offset-2 col-md-10">
            <input type="submit" value="Create" class="btn btn-primary" />
        </div>
    </div>
</div>


}

<div>
    @Html.ActionLink("Back to List", "Index")
</div>

@section Scripts {
@Scripts.Render("~/bundles/jqueryval")
}

I've found multiple tutorials regarding Html.RenderPartial, Html.Action, Ajax.BeginForm() and I haven't been able to really adapt them to what I'm trying to accomplish. I'm not opposed to sending JsonResults. I'm developing a MEAN stack app that I'm having none of these problems with, so I'm comfortable with JSON. Ultimately, I know there has to be a cleaner, more standardized way of doing this, as it seems to be a common operation of web applications.

I hate to admit, but I've been stuck at this point for three weeks, with most of my time spent in tutorials trying to find a solution. Any help would be greatly appreciated, even if it's just pointing me to a tutorial that actually has the answer I need.

natep
  • 126
  • 4
  • 13

1 Answers1

0

If I understand what you're asking correctly, the MVC answer is that your ng-repeat should be replaced by a C# foreach that iterates through the list of RequestLine and displays them. You need to create a display template and/or editor template so that you can use Html.DisplayFor() and/or Html.EditorFor() and MVC will know how to display each RequestLine. In Angular terms, your display or editor templates are whatever goes inside the ng-repeat.

If you want to create a new RequestLine without a trip to the server, you'll need javascript that copies an existing one, clears out the fields and appends it to the list. MVC does not prescribe any particular way of doing this, because it's a server-side MVC framework, not a client-side one like Angular.

If you'd like to use Angular for your client-side code, you shouldn't be using ASP.NET MVC. The two don't mix. ASP.NET MVC assumes the model is located on the server (therefore the way you iterate over items is with foreach on the server), while Angular assumes the model is located on the client (therefore you use ng-repeat). It has to be one or the other. Angular should be backed by ASP.NET Web API.

Edit after your comment:

Ajax forms are just a way to serialize a form and post it to an action method without having to write any javascript. If you wanted to you could enclose the list of RequestLine in an Ajax form and actions such as deleting, adding etc would involve a trip to the server. That wouldn't be a very good user experience.

What you want to do is build a list on the client and then just submit the whole view. There's an article at http://haacked.com/archive/2008/10/23/model-binding-to-a-list.aspx/ that describes how to do this. You are going to have to write a little messy javascript to achieve this. ASP.NET MVC doesn't do this very well, as a consequence of having the model located on the server. The upsides are things like seamless validation on the client and server using a single validation rule.

ChrisV
  • 1,309
  • 9
  • 15
  • In your second paragraph: "if you want to create a new RequestLine without a trip to the server, you'll need javascript that copies an existing one, clears out the fields and appends it to the list." Is this where Ajax forms come in? I have no problem getting rid of Angular. I used it here because I could not figure out the right solution and had to have something to demonstrate. – natep Apr 20 '15 at 12:17