Cybercop
Cybercop

Reputation: 8674

Editor template for creating radio button for enum fields not working for all enums

I have this editor template with name RadioButtonQuestion

@model Enum

@{
    // Looks for a [Display(Name="Some Name")] or a [Display(Name="Some Name", ResourceType=typeof(ResourceFile)] Attribute on your enum
    Func<Enum, string> getDescription = en =>
    {
        Type type = en.GetType();
        System.Reflection.MemberInfo[] memInfo = type.GetMember(en.ToString());

        if (memInfo != null && memInfo.Length > 0)
        {

            object[] attrs = memInfo[0].GetCustomAttributes(typeof(System.ComponentModel.DataAnnotations.DisplayAttribute),
                                                            false);

            if (attrs != null && attrs.Length > 0)
                return ((System.ComponentModel.DataAnnotations.DisplayAttribute)attrs[0]).GetName();
        }

        return en.ToString();
    };
    var listItems = Enum.GetValues(Model.GetType()).OfType<Enum>().Select(e =>
    new SelectListItem()
    {
        Text = getDescription(e),
        Value = e.ToString(),
        Selected = e.Equals(Model)
    });
    string prefix = ViewData.TemplateInfo.HtmlFieldPrefix;
    int index = 0;
    ViewData.TemplateInfo.HtmlFieldPrefix = string.Empty;

    foreach (var li in listItems)
    {
        string fieldName = string.Format(System.Globalization.CultureInfo.InvariantCulture, "{0}_{1}", prefix, index++);
        <div class="editor-radio">
            @Html.RadioButton(prefix, li.Value, li.Selected, new { @id = fieldName })
            @Html.Label(fieldName, li.Text)
        </div>
    }
    ViewData.TemplateInfo.HtmlFieldPrefix = prefix;
}

For the following model

public class CreateAppointmentSelectOptions
{      
    public QuestionStart StartQuestion { get; set; }

    public QuestionEnd EndQuestion { get; set; }   
}

public enum QuestionStart
{
    [Display(Name="Repeat till common match is found")]
    RepeatTillCommonIsFound,

    [Display(Name="Repeat once")]
    RepeatOnce,    

    [Display(Name="No repeat")]
    NoRepeat

}

public enum QuestionEnd
{
    [Display(Name="Cancel Invitation")]
    CancelInvitation,

    [Display(Name="Plan with participants on first available common date")]
    FirstAvailableCommon,

    [Display(Name="Plan with participants on my first available common date")]
    YourFirstAvailableCommon
}

with following view

@model InfoBridgeSmartDatePicker.Models.ViewModels.Appointment.CreateAppointmentSelectOptions
@{
    ViewBag.Title = "Create_WhatIf";
    Layout = "~/Views/Shared/_Layout.cshtml";
} 
@using (Html.BeginForm("Create_WhatIf", "Appointment", FormMethod.Post))
{      
    <div class="col-md-10">
        <div class='well'>
            <div class="form-group">
                <div class="input-group">
                    @Html.EditorFor(m => m.StartQuestion, "RadioButtonQuestion") &nbsp; &nbsp;
                </div>               

            </div>
        </div>
    </div>


    <div class="col-md-10">
        <div class='well'>
            <div class="form-group">         
                <div class="input-group">
                    @Html.EditorFor(m => m.EndQuestion, "RadioButtonQuestion")&nbsp;&nbsp;
                </div>                             

            </div>
        </div>
    </div>

}
@Scripts.Render("~/bundles/jquery")
@Scripts.Render("~/bundles/bootstrap")
@Scripts.Render("~/bundles/jqueryval")

the editortemplate works perfectly, it creates radiobuttons for each field of enum, but for following model

public class AttendeeResponse
{
    public IsAttending Isattending { get; set; }      
}

public enum IsAttending
{
    [Display(Name="Attending")]
    Attending,

    [Display(Name="Not attending")]
    NotAttending
}

with following view

@model InfoBridgeSmartDatePicker.Models.ViewModels.Response.AttendeeResponse
@{
    ViewBag.Title = "Index";
    Layout = "~/Views/Shared/_Layout.cshtml";
}  
@using(Html.BeginForm("Index","Response",FormMethod.Post))
{
    <div class="col-md-10">
        <div class='well'>
            <div class="form-group">
                <div class="input-group">
                    @Html.EditorFor(m => m.Isattending, "RadioButtonQuestion") &nbsp; &nbsp;
                </div>               

            </div>
        </div>
    </div>
}
<div id="provideDateTime">
    @{Html.RenderPartial("~/Views/Response/_AttendeeAvailableDateTime.cshtml");}
</div>
@Scripts.Render("~/bundles/jquery")
@Scripts.Render("~/bundles/bootstrap")
@Scripts.Render("~/bundles/jqueryval")

i get error on this line of editor template

     return en.ToString();
Line 21:     };
/*error on this line Line*/ 22:     var listItems = Enum.GetValues(Model.GetType()).OfType<Enum>().Select(e =>
Line 23:     new SelectListItem()
Line 24:     {

any idea why this editor template is not working for second view?

StackTrace:

[NullReferenceException: Object reference not set to an instance of an object.]
   ASP._Page_Views_Shared_EditorTemplates_RadioButtonQuestion_cshtml.Execute() in c:\Users\userx\Desktop\Git\DatePickerApp\datepicker\DatePicker\Views\Shared\EditorTemplates\RadioButtonQuestion.cshtml:22
   System.Web.WebPages.WebPageBase.ExecutePageHierarchy() +271
   System.Web.Mvc.WebViewPage.ExecutePageHierarchy() +120
   System.Web.WebPages.WebPageBase.ExecutePageHierarchy(WebPageContext pageContext, TextWriter writer, WebPageRenderingBase startPage) +145
   System.Web.Mvc.RazorView.RenderView(ViewContext viewContext, TextWriter writer, Object instance) +695
   System.Web.Mvc.BuildManagerCompiledView.Render(ViewContext viewContext, TextWriter writer) +382
   System.Web.Mvc.Html.TemplateHelpers.ExecuteTemplate(HtmlHelper html, ViewDataDictionary viewData, String templateName, DataBoundControlMode mode, GetViewNamesDelegate getViewNames, GetDefaultActionsDelegate getDefaultActions) +1037
   System.Web.Mvc.Html.TemplateHelpers.TemplateHelper(HtmlHelper html, ModelMetadata metadata, String htmlFieldName, String templateName, DataBoundControlMode mode, Object additionalViewData, ExecuteTemplateDelegate executeTemplate) +1633
   System.Web.Mvc.Html.TemplateHelpers.TemplateHelper(HtmlHelper html, ModelMetadata metadata, String htmlFieldName, String templateName, DataBoundControlMode mode, Object additionalViewData) +94
   System.Web.Mvc.Html.TemplateHelpers.TemplateFor(HtmlHelper`1 html, Expression`1 expression, String templateName, String htmlFieldName, DataBoundControlMode mode, Object additionalViewData, TemplateHelperDelegate templateHelper) +228
   System.Web.Mvc.Html.TemplateHelpers.TemplateFor(HtmlHelper`1 html, Expression`1 expression, String templateName, String htmlFieldName, DataBoundControlMode mode, Object additionalViewData) +140
   System.Web.Mvc.Html.EditorExtensions.EditorFor(HtmlHelper`1 html, Expression`1 expression, String templateName) +94
   ASP._Page_Views_Response_Index_cshtml.Execute() in c:\Users\userx\Desktop\Git\DatePickerApp\datepicker\DatePicker\Views\Response\Index.cshtml:14
   System.Web.WebPages.WebPageBase.ExecutePageHierarchy() +271
   System.Web.Mvc.WebViewPage.ExecutePageHierarchy() +120
   System.Web.WebPages.StartPage.RunPage() +63
   System.Web.WebPages.StartPage.ExecutePageHierarchy() +100
   System.Web.WebPages.WebPageBase.ExecutePageHierarchy(WebPageContext pageContext, TextWriter writer, WebPageRenderingBase startPage) +131
   System.Web.Mvc.RazorView.RenderView(ViewContext viewContext, TextWriter writer, Object instance) +695
   System.Web.Mvc.BuildManagerCompiledView.Render(ViewContext viewContext, TextWriter writer) +382
   System.Web.Mvc.ViewResultBase.ExecuteResult(ControllerContext context) +431
   System.Web.Mvc.ControllerActionInvoker.InvokeActionResult(ControllerContext controllerContext, ActionResult actionResult) +39
   System.Web.Mvc.ControllerActionInvoker.InvokeActionResultFilterRecursive(IList`1 filters, Int32 filterIndex, ResultExecutingContext preContext, ControllerContext controllerContext, ActionResult actionResult) +116
   System.Web.Mvc.ControllerActionInvoker.InvokeActionResultFilterRecursive(IList`1 filters, Int32 filterIndex, ResultExecutingContext preContext, ControllerContext controllerContext, ActionResult actionResult) +529
   System.Web.Mvc.ControllerActionInvoker.InvokeActionResultWithFilters(ControllerContext controllerContext, IList`1 filters, ActionResult actionResult) +106
   System.Web.Mvc.Async.<>c__DisplayClass2b.<BeginInvokeAction>b__1c() +321
   System.Web.Mvc.Async.<>c__DisplayClass21.<BeginInvokeAction>b__1e(IAsyncResult asyncResult) +185
   System.Web.Mvc.Async.WrappedAsyncResult`1.CallEndDelegate(IAsyncResult asyncResult) +42
   System.Web.Mvc.Async.WrappedAsyncResultBase`1.End() +133
   System.Web.Mvc.Async.AsyncResultWrapper.End(IAsyncResult asyncResult, Object tag) +56
   System.Web.Mvc.Async.AsyncControllerActionInvoker.EndInvokeAction(IAsyncResult asyncResult) +40
   System.Web.Mvc.Controller.<BeginExecuteCore>b__1d(IAsyncResult asyncResult, ExecuteCoreState innerState) +34
   System.Web.Mvc.Async.WrappedAsyncVoid`1.CallEndDelegate(IAsyncResult asyncResult) +70
   System.Web.Mvc.Async.WrappedAsyncResultBase`1.End() +139
   System.Web.Mvc.Async.AsyncResultWrapper.End(IAsyncResult asyncResult, Object tag) +59
   System.Web.Mvc.Async.AsyncResultWrapper.End(IAsyncResult asyncResult, Object tag) +40
   System.Web.Mvc.Controller.EndExecuteCore(IAsyncResult asyncResult) +44
   System.Web.Mvc.Controller.<BeginExecute>b__15(IAsyncResult asyncResult, Controller controller) +39
   System.Web.Mvc.Async.WrappedAsyncVoid`1.CallEndDelegate(IAsyncResult asyncResult) +62
   System.Web.Mvc.Async.WrappedAsyncResultBase`1.End() +139
   System.Web.Mvc.Async.AsyncResultWrapper.End(IAsyncResult asyncResult, Object tag) +59
   System.Web.Mvc.Async.AsyncResultWrapper.End(IAsyncResult asyncResult, Object tag) +40
   System.Web.Mvc.Controller.EndExecute(IAsyncResult asyncResult) +39
   System.Web.Mvc.Controller.System.Web.Mvc.Async.IAsyncController.EndExecute(IAsyncResult asyncResult) +39
   System.Web.Mvc.MvcHandler.<BeginProcessRequest>b__5(IAsyncResult asyncResult, ProcessRequestState innerState) +39
   System.Web.Mvc.Async.WrappedAsyncVoid`1.CallEndDelegate(IAsyncResult asyncResult) +70
   System.Web.Mvc.Async.WrappedAsyncResultBase`1.End() +139
   System.Web.Mvc.Async.AsyncResultWrapper.End(IAsyncResult asyncResult, Object tag) +59
   System.Web.Mvc.Async.AsyncResultWrapper.End(IAsyncResult asyncResult, Object tag) +40
   System.Web.Mvc.MvcHandler.EndProcessRequest(IAsyncResult asyncResult) +40
   System.Web.Mvc.MvcHandler.System.Web.IHttpAsyncHandler.EndProcessRequest(IAsyncResult result) +38
   System.Web.CallHandlerExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute() +9514812
   System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously) +155

PS: I got this editor template from this github project

Upvotes: 3

Views: 1213

Answers (2)

Daniel J.G.
Daniel J.G.

Reputation: 34992

Glad to see you already found the solution.

However you could make the editor template more robust, so it doesn't break if a null model is passed to the view and also it is compatible with nullable types like IsAttending?

Just change this code to get the enum values:

var listItems = Enum.GetValues(enumType).OfType<Enum>().Select(e =>
new SelectListItem()
{
    Text = getDescription(e),
    Value = e.ToString(),
    Selected = e.Equals(Model)
});

With this code that doesn't depend on the current model instance:

var enumType = this.ViewData.ModelMetadata.ModelType;
if (enumType.IsGenericType && enumType.GetGenericTypeDefinition() == typeof(Nullable<>))
{
    enumType = Nullable.GetUnderlyingType(enumType);
}

var listItems = Enum.GetValues(enumType).OfType<Enum>().Select(e =>
new SelectListItem()
{
    Text = getDescription(e),
    Value = e.ToString(),
    Selected = e.Equals(Model)
});

EDIT - Code used to with the Nullable enum type and null model passed to the view scenarios

Model

public class AttendeeResponse
{
    public IsAttending? Isattending { get; set; }
}

public enum IsAttending
{
    [Display(Name = "Attending")]
    Attending,

    [Display(Name = "Not attending")]
    NotAttending
}

View

@model MvcApplication1.Models.AttendeeResponse

@{
    ViewBag.Title = "Attendee Response";
}

@using(Html.BeginForm("Index","Response",FormMethod.Post))
{
    <div class="col-md-10">
        <div class='well'>
            <div class="form-group">
                <div class="input-group">
                    @Html.EditorFor(m => m.Isattending, "RadioButtonQuestion") &nbsp; &nbsp;
                </div>               

            </div>
        </div>
    </div>
}

Editor Template

@model Enum

@{
    // Looks for a [Display(Name="Some Name")] or a [Display(Name="Some Name", ResourceType=typeof(ResourceFile)] Attribute on your enum
    Func<Enum, string> getDescription = en =>
    {
        Type type = en.GetType();
        System.Reflection.MemberInfo[] memInfo = type.GetMember(en.ToString());

        if (memInfo != null && memInfo.Length > 0)
        {

            object[] attrs = memInfo[0].GetCustomAttributes(typeof(System.ComponentModel.DataAnnotations.DisplayAttribute),
                                                            false);

            if (attrs != null && attrs.Length > 0)
                return ((System.ComponentModel.DataAnnotations.DisplayAttribute)attrs[0]).GetName();
        }

        return en.ToString();
    };

    var enumType = this.ViewData.ModelMetadata.ModelType;
    if (enumType.IsGenericType && enumType.GetGenericTypeDefinition() == typeof(Nullable<>))
    {
        enumType = Nullable.GetUnderlyingType(enumType);
    }

    var listItems = Enum.GetValues(enumType).OfType<Enum>().Select(e =>
    new SelectListItem()
    {
        Text = getDescription(e),
        Value = e.ToString(),
        Selected = e.Equals(Model)
    });
    string prefix = ViewData.TemplateInfo.HtmlFieldPrefix;
    int index = 0;
    ViewData.TemplateInfo.HtmlFieldPrefix = string.Empty;

    foreach (var li in listItems)
    {
        string fieldName = string.Format(System.Globalization.CultureInfo.InvariantCulture, "{0}_{1}", prefix, index++);
        <div class="editor-radio">
            @Html.RadioButton(prefix, li.Value, li.Selected, new { @id = fieldName })
            @Html.Label(fieldName, li.Text)
        </div>
    }
    ViewData.TemplateInfo.HtmlFieldPrefix = prefix;
}

Controller

public ActionResult Attendee()
{
    //You can pass null as the view model and the view/editor template won't break
    return View();
    //This would also work, showing that you can have a nullable with null value
    //return View(new AttendeeResponse());
    //If the nullable enum has a value, it would appear as selected:
    //return View(new AttendeeResponse{Isattending = IsAttending.NotAttending});
}

Upvotes: 3

Cybercop
Cybercop

Reputation: 8674

The problem was I hadn't passed the object(the model that view is using) via the controller's get method. Once i did that it worked.

Previously I had

 public ActionResult Index()
 {

    return View();
 }

once i did

 public ActionResult Index()
 {
    var attendeeresponse = new AttendeeResponse();
    return View(attendeeresponse);
 }

it worked

Upvotes: 1

Related Questions