You are currently browsing the tag archive for the ‘Razor’ tag.

Source now available on github:

https://github.com/cbruen1/mvc4-many-to-many

This post stems from an answer I gave to a question on stackoverflow.com about saving many to many relationship data in MVC using Entity Framework 4.1 code first. The scenario is an application that creates a user and allows that user to register for one or more courses, with the courses presented as a list of check boxes. A user is able to register for many courses and a course can have many users which is where the many to many scenario comes in. The difficulty the original poster had was rendering and naming the check boxes correctly and getting them posted back in the Create view.

In this first part I will set up the overall solution and get it to a state where it will add users to the system without any courses. In part 2 I will add the necessary updates to allow one or more courses to be saved as part of a user’s profile. In part 3 I’ll show how to edit a user and change their name and the courses they’ve been registered for, display a user’s details in read only mode, and delete a user from the app.

For the project I used Visual Studio 2012 but if you don’t have this you can download the express version from here:

http://www.asp.net/mvc

or

http://www.microsoft.com/visualstudio/eng/products/visual-studio-express-products

I created a new solution containing 3 individual projects – web, domain model, and database. I used the intranet template for the web app – by default this will add a Home Controller and Home folder within the Views folder with Index, About, and Contact views. Complete the following steps:

  • Open visual Studio and create a new ASP.NET MVC4 intranet application project called MVC4ManyToMany
  • Right click the solution and add a new Class Library project called MVC4ManyToManyDomain
  • Right click the solution again and add another Class Library project called MVC4ManyToManyDatabase

In the MVC4ManyToManyDomain project the classes involved are a UserProfile class and a Course class. Delete the Class1 class and add 2 classes called Course and UserProfile.


public class Course
{
    public int CourseID { get; set; }
    public string CourseDescripcion { get; set; }
    public virtual ICollection<UserProfile> UserProfiles { get; set; }
}

public class UserProfile
{
    public UserProfile()
    {
        Courses = new List<Course>();
    }
    public int UserProfileID { get; set; }
    public string Name { get; set; }
    public virtual ICollection<Course> Courses { get; set; }
}

To set up the database and the EF DbContext delete the default Class1 class from the MVC4ManyToManyDatabase project and add a class called MVC4ManyToManyContext. You will need to reference EntityFramework and the MVC4ManyToManyDomain project and add using directives for both. To do this right click References and add MVC4ManyToManyDomain from the Solution section and Browse to {Your solution directory}\MVC4ManyToMany\packages\EntityFramework.5.0.0\lib\net45\EntityFramework.dll.


using System.Data.Entity;
using MVC4ManyToManyDomain;

namespace MVC4ManyToManyDatabase
{
    public class MVC4ManyToManyContext : DbContext
    {
        public DbSet<UserProfile> UserProfiles { get; set; }
        public DbSet<Course> Courses { get; set; }
    }
}

By default, the Entity Framework looks for a connection string named the same as the object context class. It automatically creates a SQL Server Express database in the App_Data folder using the same name as the DB Context class.

Next in the main web project add a folder called ViewModels to the Model folder. In this folder add 3 classes – UserProfileViewModel, CourseViewModel, and AssignedCourseData.


public class UserProfileViewModel
{
    public int UserProfileID { get; set; }
    public string Name { get; set; }
    public virtual ICollection<AssignedCourseData> Courses { get; set; }
}

public class CourseViewModel
{
    public int CourseID { get; set; }
    public string CourseDescripcion { get; set; }
    public virtual ICollection<UserProfileViewModel> UserProfiles { get; set; }
}

public class AssignedCourseData
{
    public int CourseID { get; set; }
    public string CourseDescription { get; set; }
    public bool Assigned { get; set; }
}

To help us transform our domain model to view model and vice versa we can create a little helper. In the web project’s Model\ViewModel folder add a static class called ViewModelHelpers (you’ll need to add “using MVC4ManyToManyDomain;”):


using MVC4ManyToManyDomain;
//...

public static class ViewModelHelpers
{
    public static UserProfileViewModel ToViewModel(this UserProfile userProfile)
    {
        var userProfileViewModel = new UserProfileViewModel
        {
            Name = userProfile.Name,
            UserProfileID = userProfile.UserProfileID
        };

        return userProfileViewModel;
    }

    public static UserProfile ToDomainModel(this UserProfileViewModel userProfileViewModel)
    {
        var userProfile = new UserProfile();
        userProfile.Name = userProfile.Name;
        userProfile.UserProfileID = userProfile.UserProfileID;

        return userProfile;
    }
}

Next, right click the Controllers folder in the web project and select Add Controller. In the dialog box name it UserProfileController and Select Empty MVC Controller from the Template drop down.
Add a reference to the MVC4ManyToManyDomain project and add these using statement to the top of the page:


using MVC4ManyToMany.Models.ViewModels;
using MVC4ManyToManyDatabase;
using MVC4ManyToManyDomain;

In the Controller paste the folowing code:


public class UserProfileController : Controller
{
    private readonly MVC4ManyToManyContext db = new MVC4ManyToManyContext();

    public ActionResult Index()
    {
        var userProfiles = db.UserProfiles.ToList();
        var userProfileViewModels = userProfiles.Select(userProfile => userProfile.ToViewModel()).ToList();

        return View(userProfileViewModels);
    }

    public ActionResult Create()
    {
        var userProfileViewModel = new UserProfileViewModel { };

        return View(userProfileViewModel);
    }

    [HttpPost]
    public ActionResult Create(UserProfileViewModel userProfileViewModel)
    {
        if (ModelState.IsValid)
        {
            var userProfile = new UserProfile { Name = userProfileViewModel.Name };

            AddOrUpdateCourses(userProfile, userProfileViewModel.Courses);
            db.UserProfiles.Add(userProfile);
            db.SaveChanges();

            return RedirectToAction("Index");
        }

        return View(userProfileViewModel);
    }

    private void AddOrUpdateCourses(UserProfile userProfile, IEnumerable<AssignedCourseData> assignedCourses)
    {
        if (assignedCourses != null)
        {
            foreach (var assignedCourse in assignedCourses)
            {
                if (assignedCourse.Assigned)
                {
                    var course = new Course { CourseID = assignedCourse.CourseID };
                    db.Courses.Attach(course);
                    userProfile.Courses.Add(course);
                }
            }
        }
    }
}

Next we will add a link to the Create user profile View from our main Index view. In the Home\Index.cshtml file add the following code:


<p>
@Html.ActionLink("User Profile Page", "Index", "UserProfile")
</p>

Next we need to add 2 Views for our user profile, Index and Create. In the Views folder of the web project right click and select Add folder. Name the folder “UserProfile”. In the Index action of the UserProfileController right click and select “Add View”:

– Name the View “Index”
– Select the Razor View engine
– Click “Create strongly typed view” and select the UserProfileViewModel class as the Model class
– In the scaffold template select “List”. Check the “Use a layout or master page” check box

This adds a view that will display all the users in our system and also adds a link to our Create view which we will add next. In the Create action (NOT the one decorated with the “HttpPost” attribute) of the UserProfileController right click and select “Add View”:

– Name the View “Create”
– Select the Razor View engine
– Click “Create strongly typed view” and select the UserProfileViewModel class as the Model class
– In the scaffold template select “Create”. Check the “Use a layout or master page” check box
– Open the Create.cshtml view and delete the fields that refer to UserProfileID if present

Now we have a view that will allow us to add a user to the system. Run the application which takes us to the main Index page. Click on the link to the Create user profile page that we added earlier. We are now able to add users to the system.  All we have is one field for a name but it demonstrates the concept and you’re free to add other fields to the model as you see fit.

That’s it for the first part. This is a pretty basic and simplified example to get up and running with this scenario. In the real world we would ideally do this using Test Driven Development (TDD) and have a test project in the solution, and also add error handling, dependency injection, and other such stuff to make it more robust and testable.

In part 2  I’ll show how to add courses to the system, how to list all the courses when creating a new user, and how to save one or more courses as part of a user’s profile.

 

Advertisements

Recently I had a scenario where I needed to pass a value to a web service when a user selected an option in a drop down list. I had the drop down in a Razor view and when an item was selected an associated value needed to be passed to a web service as a parameter. However the value to be passed was not the id or value in the drop down but a different value associated with the selected item. So I needed a way to store this associated value in the drop down along with each displayed item.

In the database each item had 3 values associated with it – the database id, short name (for display), and long name (for passing to a service). The drop down needed to show the short name, so I populated the drop down with the database id as the value and the short name as the text.

As an example the drop down had values such as Cortina, Boxster, and Viper. When Cortina is seleted “Ford Cortina 1979 Blue” needed to be passed to the service. The solution I came up with was to store the long name as a data dash attribute in each item in the drop down. So here’s the data:

CARID SHORT_NAME LONG_NAME
1     Viper     Dodge Viper 1982
2     Boxster   Porsche Boxster 2009 Black
3     Cortina   Ford Cortina 1979 Blue

I added a JsonResult method on the Controller to return an anonymous object with the 3 properties that I wanted:


public class VehicleController : Controller
{
    // etc.
    public JsonResult GetSubTypesForTypeType(string typeTypeName)
    {
        var cars = repository.GetTypeWithSubTypes(typeTypeName);

        return cars == null
        ? Json(new object[0], JsonRequestBehavior.AllowGet)
        : Json(cars.SubTypes.OrderBy(s => s.Name).Select(
            s => new { s.SubTypeID, s.Name, s.Description }).ToArray(),
            JsonRequestBehavior.AllowGet);
    }
    // etc.
}

Here’s the code I use to create the drop down:

    <div class="editor-field">
        @Html.DropDownListFor(model => model.CarID,
            new SelectList(ViewBag.Cars, "Value", "Text", "1"))
        @Html.ValidationMessageFor(model => model.CarShortName)
    </div>

Then in js:

Populate the drop down:


// populate the cars drop down when the select list is available
if ($('select#SubTypeID').length) {
    var carsSelect = $('select#SubTypeID');
    var carsList = populateCarsList("CARS");
    var carsListHtml = createCarsSelectList(carsList);
    carsSelect.html('');
    carsSelect.append(carsListHtml);
    $('#SubTypeID').change(function (e) {
        clearFormData();
    });
}

Call a function to get the subtypes (cars) via an ajax call:

function populateCarsList(typeTypeName) {
    var carsList;
    $.ajax({
        url: '/Vehicle/GetSubTypesForTypeType',
        data: { typeTypeName: typeTypeName },
        async: false
    }).done(function (data) {
        carsList = data;
    }).error(function (msg, url, line) {
            alert("Error - error message: " + line);
    });

    return carsList;
}

Function to create the select list with the added description as a “data-*” attribute:

function createCarsSelectList(selectData) {
    var html = '',
    len = selectData.length,
    selected,
    description;

    for (var i = 0; i &lt; len; i++) {
        // "Viper" should be selected by default
        if (selectData[i].Name.toLocaleUpperCase() === "VIPER") {
            selected = ' selected="selected" ';
        } else {
            selected = '';
        }

        // Add the description (as a "data-" attribute), some are null
        if (selectData[i].Description != null) {
            description = selectData[i].Description;
        } else {
            description = '';
        }

        html += '<option value="' + selectData[i].SubTypeID + '" data-description="' + description + '"' + selected + '>' + selectData[i].Name + '</option>';
    }

    return html;
}

On my current project I’ve been working with a concept called a time point, which allows a user to schedule certain events that need to happen at specific points of time. Up until now I had been using the same partial view rendered from different parent views (which were also partial views within an overall view). This caused problems when rendering fields using EditorFor and DropDownListFor methods, as these methods use the model name to create the field names and ids. Because the model was the same, fields called from different parent views were being rendered with the same names and ids. To work around this I had to use an if-else to decide on the prefix that we needed, depending on a certain property in the model which was set in the parent view. This approach was quite clunky and Editor templates negate the need for this as they render the correct ids and names automatically.

An Editor template is a partial view that’s placed by convention in a directory called “EditorTemplates” in Views/Shared, and again by convention is named the same as the view model containing the fields we want to render:

 

Image

 

So in this case my ViewModel is called DiscreteTimepointViewModel.cs:

/// <summary>
/// ViewModel for discrete timepoints
/// </summary>
public class DiscreteTimepointViewModel
{
/// <summary>
/// Gets or sets the TimePointType associated with this StudyEvent
/// </summary>
public int TimePointType { get; set; }
/// <summary>
/// Gets or sets a time points string which can serve as an input for generating an array of time points. This property
/// is only used by the razor views to generate HTML elements using the HTML helpers.
/// </summary>
public double? TimePoint { get; set; }
/// <summary>
/// Gets or sets the ID of the time unit for the specified TimePoint.
/// </summary>
[Required]
public int? TimeUnitID { get; set; }
/// <summary>
/// Gets or sets the model name
/// </summary>
public string ModelName { get; set; }
/// <summary>
/// Gets or sets the left hand field binding name
/// </summary>
public string LeftHandFieldBindingName { get; set; }
}

After adding the template view and view model I then add a view model property to each parent view that I want to render it from:

/// <summary>
/// Gets or sets the view model for discrete time points
/// </summary>
public DiscreteTimepointViewModel DiscreteTimePoint { get; set; }

So using the above I render the time point fields from a view as such:


@{Model.defaultStudyEvent = new DiscreteTimepointViewModel { TimeUnitID = 1, ModelName = "defaultStudyEvent", LeftHandFieldBindingName = "leftHandStudyEvent" }; }
@Html.EditorFor(x => x.defaultStudyEvent, "DiscreteTimepointViewModel")

 

%d bloggers like this: