2

I have the following cshtml code in a View called ViewDepartment.cshtml

@model DepartmentViewModel

<head>
    <meta charset="iso-8859-1">
</head>
<body>
    <div class="d-flex flex-row">
        <a class="navbar-text">@Html.DisplayNameFor(o => o.HeadOfTheDepartment): </a>
        <a class="nav-link" asp-action="Profile" asp-controller="Faculty" asp-route-Id="@Model.HeadOfTheDepartment.FacultyId">@Html.DisplayFor(o => o.HeadOfTheDepartment.FacultyName)</a>
    </div>
    @*Professors*@
    <div class="d-flex flex-column">
        <a class="navbar-text align-content-center">@Html.DisplayNameFor(o => o.Professors): </a>
        <table class="table table-bordered">
            <thead>
                <tr class="d-table-row">
                    <th>@Html.DisplayNameFor(o => o.Professors.First().FacultyName)</th>
                    <th>@Html.DisplayNameFor(o => o.Professors.First().FacultyDepartment)</th>
                    <th>@Html.DisplayNameFor(o => o.Professors.First().FacultyDesignation)</th>
                    <th>@Html.DisplayNameFor(o => o.Professors.First().FacultyAddress)</th>
                    <th>@Html.DisplayNameFor(o => o.Professors.First().FacultyEmail)</th>
                    <th>@Html.DisplayNameFor(o => o.Professors.First().FacultyPhoneNumber)</th>
                    <th>@Html.DisplayNameFor(o => o.Professors.First().FacultyStatus)</th>
                    <th></th>
                </tr>
            </thead>
            <tbody>
                @foreach (Faculty faculty in Model.Professors)
                {
                    <tr class="d-table-row">
                        <td>@Html.DisplayFor(o => faculty.FacultyName)</td>
                        <td>@Html.DisplayFor(o => faculty.FacultyDepartment.DepartmentName)</td>
                        <td>@Html.DisplayFor(o => faculty.FacultyDesignation)</td>
                        <td>@Html.DisplayFor(o => faculty.FacultyAddress)</td>
                        <td>@Html.DisplayFor(o => faculty.FacultyEmail)</td>
                        <td>@Html.DisplayFor(o => faculty.FacultyPhoneNumber)</td>
                        <td>@Html.DisplayFor(o => faculty.FacultyStatus)</td>
                        <td><a class="nav-link" asp-action="Profile" asp-controller="Faculty" asp-route-Id="@faculty.FacultyId">View Profile</a></td>
                    </tr>
                }
            </tbody>
        </table>
    </div>
    @*Associate Professors*@
    <div class="d-flex flex-column">
        <a class="navbar-text align-content-center">@Html.DisplayNameFor(o => o.AssociateProfessors): </a>
        <table class="table table-bordered">
            <thead>
                <tr class="d-table-row">
                    <th>@Html.DisplayNameFor(o => o.AssociateProfessors.First().FacultyName)</th>
                    <th>@Html.DisplayNameFor(o => o.AssociateProfessors.First().FacultyDepartment)</th>
                    <th>@Html.DisplayNameFor(o => o.AssociateProfessors.First().FacultyDesignation)</th>
                    <th>@Html.DisplayNameFor(o => o.AssociateProfessors.First().FacultyAddress)</th>
                    <th>@Html.DisplayNameFor(o => o.AssociateProfessors.First().FacultyEmail)</th>
                    <th>@Html.DisplayNameFor(o => o.AssociateProfessors.First().FacultyPhoneNumber)</th>
                    <th>@Html.DisplayNameFor(o => o.AssociateProfessors.First().FacultyStatus)</th>
                    <th></th>
                </tr>
            </thead>
            <tbody>
                @foreach (var faculty in Model.AssociateProfessors)
                {
                    <tr class="d-table-row">
                        <td>@Html.DisplayFor(o => faculty.FacultyName)</td>
                        <td>@Html.DisplayFor(o => faculty.FacultyDepartment.DepartmentName)</td>
                        <td>@Html.DisplayFor(o => faculty.FacultyDesignation)</td>
                        <td>@Html.DisplayFor(o => faculty.FacultyAddress)</td>
                        <td>@Html.DisplayFor(o => faculty.FacultyEmail)</td>
                        <td>@Html.DisplayFor(o => faculty.FacultyPhoneNumber)</td>
                        <td>@Html.DisplayFor(o => faculty.FacultyStatus)</td>
                        <td><a class="nav-link" asp-action="Profile" asp-controller="Faculty" asp-route-Id="@faculty.FacultyId">View Profile</a></td>
                    </tr>
                }
            </tbody>
        </table>
    </div>
    @*Assistant Professors*@
    <div class="d-flex flex-column">
        <a class="navbar-text align-content-center">@Html.DisplayNameFor(o => o.AssistantProfessors): </a>
        <table class="table table-bordered">
            <thead>
                <tr class="d-table-row">
                    <th>@Html.DisplayNameFor(o => o.AssistantProfessors.First().FacultyName)</th>
                    <th>@Html.DisplayNameFor(o => o.AssistantProfessors.First().FacultyDepartment)</th>
                    <th>@Html.DisplayNameFor(o => o.AssistantProfessors.First().FacultyDesignation)</th>
                    <th>@Html.DisplayNameFor(o => o.AssistantProfessors.First().FacultyAddress)</th>
                    <th>@Html.DisplayNameFor(o => o.AssistantProfessors.First().FacultyEmail)</th>
                    <th>@Html.DisplayNameFor(o => o.AssistantProfessors.First().FacultyPhoneNumber)</th>
                    <th>@Html.DisplayNameFor(o => o.AssistantProfessors.First().FacultyStatus)</th>
                    <th></th>
                </tr>
            </thead>
            <tbody>
                @foreach (var faculty in Model.AssistantProfessors)
                {
                    <tr class="d-table-row">
                        <td>@Html.DisplayFor(o => faculty.FacultyName)</td>
                        <td>@Html.DisplayFor(o => faculty.FacultyDepartment.DepartmentName)</td>
                        <td>@Html.DisplayFor(o => faculty.FacultyDesignation)</td>
                        <td>@Html.DisplayFor(o => faculty.FacultyAddress)</td>
                        <td>@Html.DisplayFor(o => faculty.FacultyEmail)</td>
                        <td>@Html.DisplayFor(o => faculty.FacultyPhoneNumber)</td>
                        <td>@Html.DisplayFor(o => faculty.FacultyStatus)</td>
                        <td><a class="nav-link" asp-action="Profile" asp-controller="Faculty" asp-route-Id="@faculty.FacultyId">View Profile</a></td>
                    </tr>
                }
            </tbody>
        </table>
    </div>
    @*Lecturers*@
    <div class="d-flex flex-column">
        <a class="navbar-text align-content-center">@Html.DisplayNameFor(o => o.Lecturers): </a>
        <table class="table table-bordered">
            <thead>
                <tr class="d-table-row">
                    <th>@Html.DisplayNameFor(o => o.Lecturers.First().FacultyName)</th>
                    <th>@Html.DisplayNameFor(o => o.Lecturers.First().FacultyDepartment)</th>
                    <th>@Html.DisplayNameFor(o => o.Lecturers.First().FacultyDesignation)</th>
                    <th>@Html.DisplayNameFor(o => o.Lecturers.First().FacultyAddress)</th>
                    <th>@Html.DisplayNameFor(o => o.Lecturers.First().FacultyEmail)</th>
                    <th>@Html.DisplayNameFor(o => o.Lecturers.First().FacultyPhoneNumber)</th>
                    <th>@Html.DisplayNameFor(o => o.Lecturers.First().FacultyStatus)</th>
                    <th></th>
                </tr>
            </thead>
            <tbody>
                @foreach (var faculty in Model.Lecturers)
                {
                    <tr class="d-table-row">
                        <td>@Html.DisplayFor(o => faculty.FacultyName)</td>
                        <td>@Html.DisplayFor(o => faculty.FacultyDepartment.DepartmentName)</td>
                        <td>@Html.DisplayFor(o => faculty.FacultyDesignation)</td>
                        <td>@Html.DisplayFor(o => faculty.FacultyAddress)</td>
                        <td>@Html.DisplayFor(o => faculty.FacultyEmail)</td>
                        <td>@Html.DisplayFor(o => faculty.FacultyPhoneNumber)</td>
                        <td>@Html.DisplayFor(o => faculty.FacultyStatus)</td>
                        <td><a class="nav-link" asp-action="Profile" asp-controller="Faculty" asp-route-Id="@faculty.FacultyId">View Profile</a></td>
                    </tr>
                }
            </tbody>
        </table>
    </div>
</body>

It can be seen that for 4 different designations, namely, Professor, AssociateProfessor, AssistantProfessor and Lecturer, all of which are of the type Designation which is an Enum:

public Enum Designation
{
    Professor,
    AssociateProfessor,
    AssistantProfessor,
    Lecturer
}

I had to write the same code framework, namely,

@*Professors*@
<div class="d-flex flex-column">
    <a class="navbar-text align-content-center">@Html.DisplayNameFor(o => o.Professors): </a>
    <table class="table table-bordered">
        <thead>
            <tr class="d-table-row">
                <th>@Html.DisplayNameFor(o => o.Professors.First().FacultyName)</th>
                <th>@Html.DisplayNameFor(o => o.Professors.First().FacultyDepartment)</th>
                <th>@Html.DisplayNameFor(o => o.Professors.First().FacultyDesignation)</th>
                <th>@Html.DisplayNameFor(o => o.Professors.First().FacultyAddress)</th>
                <th>@Html.DisplayNameFor(o => o.Professors.First().FacultyEmail)</th>
                <th>@Html.DisplayNameFor(o => o.Professors.First().FacultyPhoneNumber)</th>
                <th>@Html.DisplayNameFor(o => o.Professors.First().FacultyStatus)</th>
                <th></th>
            </tr>
        </thead>
        <tbody>
            @foreach (Faculty faculty in Model.Professors)
            {
                <tr class="d-table-row">
                    <td>@Html.DisplayFor(o => faculty.FacultyName)</td>
                    <td>@Html.DisplayFor(o => faculty.FacultyDepartment.DepartmentName)</td>
                    <td>@Html.DisplayFor(o => faculty.FacultyDesignation)</td>
                    <td>@Html.DisplayFor(o => faculty.FacultyAddress)</td>
                    <td>@Html.DisplayFor(o => faculty.FacultyEmail)</td>
                    <td>@Html.DisplayFor(o => faculty.FacultyPhoneNumber)</td>
                    <td>@Html.DisplayFor(o => faculty.FacultyStatus)</td>
                    <td><a class="nav-link" asp-action="Profile" asp-controller="Faculty" asp-route-Id="@faculty.FacultyId">View Profile</a></td>
                </tr>
            }
        </tbody>
    </table>
</div>

4 times! (changing the Professors to Lecturers and the likes, of course)


This seems redundant, and I wanted to know if there was a way that would allow me to write the framework only once which will run for all 4 types of Designation. That way, even if the any more Designations get added later, it'll automatically be accomodated in the current code.

Professors (and its sister properties) are of the type IList<Faculty>, where Faculty class is as follows:

public class Faculty
{
    [Required]
    public string FacultyId { get; set; }
    
    [Required]
    public string FacultyName { get; set; }
    
    [Required]
    public FacultyStatus FacultyStatus { get; set; }
    
    [Required]
    public Designation FacultyDesignation { get; set; }

    [Required]
    [DataType(DataType.EmailAddress)]
    public string FacultyEmail { get; set; }

    [Required]
    [DataType(DataType.PhoneNumber)]
    public string FacultyPhoneNumber { get; set; }

    [Required]
    public string FacultyAddress { get; set; }
    
    [Required]
    public int FacultyExperience { get; set; }
    
    [Required]
    public string FacultyQualifications { get; set; }
    
    [Required]
    public string FacultyDescription { get; set; }

    public Department HODDepartment { get; set; }

    [ForeignKey("FacultyDepartment")]
    public string FacultyDepartmentId { get; set; }
    public Department FacultyDepartment{ get; set; }

}


I tried an approach using reflection (available in the previous version of the post) not very succesfully. I am guessing, that some version of ForEach loop will be required, which will loop over an object of Enum.GetNames(typeof(Designation)).Cast<Designation>(). However, I am unable to figure out the Reflection constructs I'll need, for I am pretty new to the concept of Reflection. I need someone to point me in the right direction.

kesarling He-Him
  • 1,944
  • 3
  • 14
  • 39

1 Answers1

1

I'm working off of this as a base of what your domain resembles based on your question.

public enum Designation
{
    professor,
    associate_professor,
    assistant_professor,
    lecturer
}

public static class ReflectionHelpers
{
    // this code can go anywhere and doesn't need to belong in your domain
    public static string GetAttributeDisplayName(PropertyInfo property)
    {
        var atts = property.GetCustomAttributes(
            typeof(DisplayNameAttribute), true);
        if (atts.Length == 0)
            return null;
        return (atts[0] as DisplayNameAttribute).DisplayName;
    }
}

public class Teacher
{
    public int FacultyId { get; }

    [DisplayName("Name")]
    public string FacultyName { get; set; }

    [DisplayName("Department")]
    public FacultyDepartment FacultyDepartment { get; set; }

    [DisplayName("Designation")]
    public string FacultyDesignation { get; set; }

    [DisplayName("Address")]
    public string FacultyAddress { get; set; }

    [DisplayName("Email")]
    public string FacultyEmail { get; set; }

    [DisplayName("Phone")]
    public string FacultyPhoneNumber { get; set; }

    [DisplayName("Status")]
    public string FacultyStatus { get; set; }
}

public class FacultyDepartment
{
    [DisplayName("Department Name")]
    public string DepartmentName { get; }
}

[AttributeUsage(AttributeTargets.Property)]
public class DesignationAttribute : Attribute
{
    public Designation Designation;

    public DesignationAttribute(Designation designation)
    {
        Designation = designation;
    }
}

public class DomainModel
{
    [DisplayName("Professors")]
    [Designation(Designation.professor)]
    public List<Teacher> Professors { get; } = new List<Teacher>();

    [DisplayName("Associate Professors")]
    [Designation(Designation.associate_professor)]
    public List<Teacher> AssociateProfessors { get; } = new List<Teacher>();

    [DisplayName("Assistant Professors")]
    [Designation(Designation.assistant_professor)]
    public List<Teacher> AssistantProfessors { get; } = new List<Teacher>();

    [DisplayName("Lecturers")]
    [Designation(Designation.lecturer)]
    public List<Teacher> Lecturers { get; } = new List<Teacher>();

    public DomainModel()
    {
        Professors.Add(new Teacher { FacultyName = "Professor 1" });
        AssociateProfessors.Add(new Teacher { FacultyName = "Associate Professor 1" });
        AssistantProfessors.Add(new Teacher { FacultyName = "Assistant Professor 1" });
        Lecturers.Add(new Teacher { FacultyName = "Lecturer 1" });
    }
}

What we need here is reflection. Now, we'll iterate the Enum.GetValues() rather than the GetNames() and find the appropriate Model property based on the custom attribute we assigned. If your Model's properties have the same name as the enum value, you can throw out the DesignationAttribute bits. In the view, we do your same loop basically, but immediately lookup the property we're working with via reflection. (research more on this topic to get a better understanding, no need to regurgitate basic reflection here).

I get your main Model DisplayName from the reflected ProperyInfo that we already have in our enum loop. I do this via a ReflectionHelpers static method but you can move this method anywhere, such as a top-level library.

We then use modelProperty.GetValue(Model) to reflect the value of the property back from the Model instance so we can iterate the list contents of that property.

The rest is just as you had it previously.

@using System.Reflection;

@model DomainModel
@{
    ViewData["Title"] = "Home Page";

    var modelProperties = Model.GetType().GetProperties();
    var teacherProperties = typeof(Teacher).GetProperties();
}

@foreach (var designation in Enum.GetValues<Designation>()) // use GetValues now
{
    var modelProperty =
        modelProperties
            .FirstOrDefault(p => p.GetCustomAttribute<DesignationAttribute>().Designation == designation);

    <div class="d-flex flex-column">
        <a class="navbar-text align-content-center">@ReflectionHelpers.GetAttributeDisplayName(modelProperty): </a>
        <table class="table table-bordered">
            <thead>
                <tr class="d-table-row">
                    @foreach (var teacherProperty in teacherProperties)
                    {
                        <th>@ReflectionHelpers.GetAttributeDisplayName(teacherProperty)</th>
                    }
                    <th></th>
                </tr>
            </thead>
            <tbody>
                @foreach (var faculty in modelProperty.GetValue(Model) as IList<Teacher>)
                {
                    <tr class="d-table-row">
                        <td>@Html.DisplayFor(o => faculty.FacultyName)</td>
                        <td>@Html.DisplayFor(o => faculty.FacultyDepartment.DepartmentName)</td>
                        <td>@Html.DisplayFor(o => faculty.FacultyDesignation)</td>
                        <td>@Html.DisplayFor(o => faculty.FacultyAddress)</td>
                        <td>@Html.DisplayFor(o => faculty.FacultyEmail)</td>
                        <td>@Html.DisplayFor(o => faculty.FacultyPhoneNumber)</td>
                        <td>@Html.DisplayFor(o => faculty.FacultyStatus)</td>
                        <td>
                            <a class="nav-link" asp-action="Profile" asp-controller="Faculty" asp-route-Id="@faculty.FacultyId">View Profile</a>
                        </td>
                    </tr>
                }
            </tbody>
        </table>
    </div>
}
Dave Jellison
  • 924
  • 13
  • 22
  • 1
    I was trying to work out a solution of my own all this while. Can't say I did not come close. However, I followed a different approach (which I am guessing is wrong). Just one more question, can we in some way generalize the DisplayNameFor function too? instead of `o.Professors`, it'll use `o.`. Because, you see, the whole reason for using this approach, was to be able to generalize the content of DisplayNameFor method :) – kesarling He-Him Nov 03 '21 at 21:13
  • 1
    Certainly, answer updated. Using the same technique, we can just loop the properties of the Teacher type as well. Note, if you have properties that you want to omit, you'll need to filter those in typeof(Teacher).GetProperties() inside the view code. – Dave Jellison Nov 03 '21 at 21:19
  • 1
    Example if you need to skip properties: var teacherProperties = typeof(Teacher).GetProperties().Where(p => new string[]{ "Hidden Property 1","Hidden Property 2" }.Contains(p.Name) == false); – Dave Jellison Nov 03 '21 at 21:20
  • 1
    Thanks man! I'll go through the new approach. In the meantime, will you please checkout the edit I made to the post and tell me if I was ever on the right track? I essentially wanted to get the Return Type of the `First()` method of `Generic.List` and then use `GetProperty` on that, but `GetMethods()` and `GetMethod("First")` keep returning null for some reason :( – kesarling He-Him Nov 03 '21 at 21:22
  • Also, the generic model supplied to GetCustomAttribute, is that to be taken as `Designation`? The compiler can't seem to find the `DesignationAttribute` – kesarling He-Him Nov 03 '21 at 21:31
  • 1
    I'd probably need to see what your actual classes looked like to visually debug that. However, what you're saying to me in reflection code is something like "find the property 'Professors' as per my domain example, then, give me its type which would be List, then give me the methods of the List type. I'm not sure why it was totally null, however. With reflection, try saving out each step of what you're doing and debug it. I think you were on the right track, but a little off in trying to get the methods of the Type of the Property in this case, there's a faster route (mine hehe). – Dave Jellison Nov 03 '21 at 21:33
  • 1
    DesignationAttribute is in the top block of code I provided. Also, here's a link to the final results just so we know we're on the same page: https://imgur.com/a/jWxTbIA – Dave Jellison Nov 03 '21 at 21:34
  • 1
    Wow! Nice :) Um... about that thing with the GetMethods, what I was essentially trying to do was, to access the `First()` method of the List, and then Invoke that, Kinda like `First().FacultyName` – kesarling He-Him Nov 03 '21 at 21:36
  • 1
    It's OK, reflection can be tough at first and best to debug thoroughly. We can continue talking in chat if you need more help. SO is complaining at us :) – Dave Jellison Nov 03 '21 at 21:37
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/238854/discussion-between-kesarling-he-him-and-dave-jellison). – kesarling He-Him Nov 04 '21 at 03:40