Search code examples
c#asp.net-mvcrazorasp.net-mvc-5html.actionlink

Using an HTML ActionLink route argument when having a "dual" view model


In my ASP.NET MVC 5 application I have the following index view of projects (retrieved from the Projects table in the database).

@using Leepio.Models
@using Microsoft.AspNet.Identity
@model ApplicationTwoViewModel

@{
    ViewBag.Title = "Index";
}

<h2>Index</h2>

<p>
    @Html.ActionLink("Create New", "Create" ,"Projects")
</p>
<table class="table">
    <tr>
        <th>
            @Html.ActionLink("Title", "Index",  new {SortOrder = (ViewBag.SortOrder==null?"Asc":(ViewBag.SortOrder=="Asc"?"Desc":"Asc")), SortBy = "Title"})

        </th>
        <th>
            @Html.ActionLink("Application Deadline", "Index", new {SortOrder = (ViewBag.SortOrder == null ? "Asc" : (ViewBag.SortOrder == "Asc" ? "Desc" : "Asc")), SortBy = "ApplicationDeadline"}) <br/>
            @Html.ActionLink("Hourly Rate (DKK)", "Index", new { SortOrder = (ViewBag.SortOrder == null ? "Asc" : (ViewBag.SortOrder == "Asc" ? "Desc" : "Asc")), SortBy = "HourlyRate" })
        </th>
       <th>
            @Html.ActionLink("Skill requirements", "Index", new { SortOrder = (ViewBag.SortOrder == null ? "Asc" : (ViewBag.SortOrder == "Asc" ? "Desc" : "Asc")), SortBy = "RequiredSkills" })
        </th>


    </tr>

@foreach (var item in Model.Model1) {
    <tr>
        <td>
            @Html.DisplayFor(modelItem => item.Title)
        </td>
        <td>
            @Html.DisplayFor(modelItem => item.ApplicationDeadline)<br/>
            @Html.DisplayFor(modelItem => item.HourlyRate)
        </td>
       <td>
            @Html.DisplayFor(modelItem => item.RequiredSkills)
        </td>


        <td>

            @if(item.UserId == User.Identity.GetUserId()) 

            {
                @Html.ActionLink("Edit", "Edit",  new {id = item.ProjectId})

                @Html.ActionLink("Delete", "Delete", new {id = item.ProjectId}) 
            }

            @Html.ActionLink("Details", "Details", new {id = item.ProjectId}) |

            @Html.ActionLink("Apply", "Create", "Applications" , new { id = Model.Model2.ApplicationId }) |
        </td>
    </tr>
}

</table>
@{
    double TotalPage = @ViewBag.TotalPages;
}

<ul class="pagination">
    @for (int i = 1; i <= TotalPage; i++)
    {

        if (i == ViewBag.Page)
        {
            <li class="active"> @Html.ActionLink(i.ToString() + " ", "Index", "Projects", new { SortOrder = (ViewBag.SortOrder == null ? "Asc" : ViewBag.SortOrder), SortBy = (ViewBag.SortBy == null ? "Title" : ViewBag.SortBy), Page = i })</li>
        }
        else
        {
            <li>
                @Html.ActionLink(i.ToString() + " ", "Index", "Projects", new { SortOrder = (ViewBag.SortOrder == null ? "Asc" : ViewBag.SortOrder), SortBy = (ViewBag.SortBy == null ? "Title" : ViewBag.SortBy), Page = i })
            </li>
        }

    }
</ul>

I am using the ApplicationTwoViewModel I have created, which basically has two view models in it:

public class ApplicationTwoViewModel
{
    public IEnumerable<Project> Model1 { get; set; }
    public Application Model2 { get; set; }
}

I am trying to make this ActionLink

 @Html.ActionLink("Apply", "Create", "Applications" , new { id = Model.Model2.ApplicationId }) |

Create a new "Application" from the Create ActionRestult that is in the ApplicationsController:

public ActionResult Create()
{
    return View();
}

// POST: Applications/Create
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include = "ApplicationId,ProjectId,UserId,CoverLetter")] Application application)
{
    if (ModelState.IsValid)
    {
        db.Applications.Add(application);
        db.SaveChanges();
        return RedirectToAction("Index");
    }

    return View(application);

Application Model:

public class Application
{
    public int ApplicationId { get; set; }
    public int ProjectId { get; set; }
    public string UserId { get; set; }
    public string CoverLetter { get; set; }
}

So right now I am trying to make the "Apply" ActionLink so the Create view receives the ProjectId from the Projects Index view, like the Edit/Delete/Details receives the ProjectId and am not sure how to do this.

Right now it works but you have to manually insert the ProjectId and the UserId (the one logged in, should be User.Identity.GetUserId() but I am not sure where to add it in this context), and I want them to be retrieved behind the scenes so the user only has to write the CoverLetter and then creates the application.


Solution

  • You need to add a parameter in the Create() method and the initialize you model with that value and pass it to the view

    public ActionResult Create(int projectId)
    {
        Application model = new Application()
        {
            ProjectId = projectId 
        };
        return View(model);
    }
    

    and modify you link to pass the value (currently its adding the value as a html attribute, not a route value (note the 5th parameter)

    @Html.ActionLink("Apply", "Create", "Applications" , new { projectId = item.ProjectId }, null)
    

    Then in the view, include a hidden input for ProjectId so its submitted to the POST value

    @Html.HiddenFor(m => m.ProjectId)
    

    Note that your UserID value should be added in the POST method before you save the Application (not in the model or in the view)

    [HttpPost]
    [ValidateAntiForgeryToken]
    public ActionResult Create([Bind(Include = "ProjectId, CoverLetter")] Application application)
    {
        if (ModelState.IsValid)
        {
            applicatio.UserId = User.Identity.GetUserId(); // set the user here
            db.Applications.Add(application);
            db.SaveChanges();
            return RedirectToAction("Index");
        }
        return View(application);
    }
    

    Side note: Its not really clear what the purpose of your ApplicationTwoViewModel model is for. All you ever use in the view is IEnumerable<Project> so your Model2 property seems unnecesary