Ho uno strano problema con una delle mie viste Razor in un'applicazione ASP.NET MVC3. Ricevo un errore che mi dice che non è possibile trovare una proprietà, quando la proprietà sembra esistere quando scrivo il suo valore sulla console del debugger.Razor e ereditarietà dell'interfaccia in ASP.NET MVC3: perché non è possibile trovare questa proprietà?
La mia vista prende come modello una classe denominata FormEditViewModel. FormEditViewModel ha una proprietà di tipo IForm, un'interfaccia che eredita da un'altra interfaccia, IFormObject. IFormObject definisce un nome di proprietà, quindi qualsiasi implementazione di IForm deve implementare una proprietà denominata Nome. Il tipo concreto Form implementa l'interfaccia IForm e definisce una proprietà Name come richiesto.
Quando eseguo il codice e ispeziono l'oggetto FormEditViewModel passato alla vista, posso vedere che ha una proprietà Form, di tipo IForm e questo oggetto Form ha una proprietà Name. Se inserisco la seguente riga nel mio controller, per scrivere il valore di FormEditViewModel.Name appena prima che si passa alla vista, la finestra di output viene visualizzato il nome corretto:
Debug.WriteLine("Name: " + vm.Form.Name);
Eppure, quando ho eseguito la vista che ottenere un errore dicendo che "La proprietà MyCompany.MyApplication.Domain.Forms.IForm.Name non è stata trovata." Perché Razor non può trovare la proprietà Name quando il codice C# nel controller è evidentemente in grado?
La mia vista va così. La riga che genera l'eccezione è @ Html.LabelFor (model => model.Form.Name, "Titolo modulo").
@using MyCompany.MyApplication.ViewModels;
@model FormEditViewModel
@{
ViewBag.Title = "Edit form";
}
<script src="@Url.Content("~/Scripts/jquery.validate.min.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")" type="text/javascript"></script>
<div class="MyApplicationcontainer">
@using (Html.BeginForm("UpdateForm", "ZooForm"))
{
<div class="formHeader">
@Html.ValidationSummary(true)
@Html.Hidden("id", Model.Form.ZooFormId)
<div id="editFormTitleDiv">
<div class="formFieldContainer">
@Html.Label("Form ID")
@Html.TextBoxFor(m => m.Form.ZooFormId, new { @disabled = true })
</div>
<div class="formFieldContainer">
@Html.LabelFor(model => model.Form.Name, "Form title")
@Html.EditorFor(model => model.Form.Name)
@Html.ValidationMessageFor(model => model.Form.Name)
</div>
...
Ecco il modello di vista:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using MyCompany.MyApplication.Domain.Forms;
using MyCompany.App.Web.ViewModels;
namespace MyCompany.MyApplication.ViewModels
{
public class FormEditViewModel : ViewModelBase
{
public IForm Form { get; set; }
public int Id
{
get { return Form.ZooFormId; }
}
public IEnumerable<Type> Types { get; set; }
public Dictionary<string, string> FriendlyNamesForTypes { get; set; }
public Dictionary<string, string> FriendlyNamesForProperties { get; set; }
public IEnumerable<String> PropertiesForUseInForms { get; set; }
public ObjectBrowserTreeViewModel ObjectBrowserTreeViewModel { get; set; }
}
}
oggetto la forma è davvero lunga quindi non mi incollare il tutto qui. Si dichiara così:
public class Form : FormObject, IForm
oggetto la forma non ridefinire la proprietà Name, ma eredita dalla classe FormObject. FormObject inizia così:
public abstract class FormObject : IFormObject
Ecco l'interfaccia IForm. Come si può vedere, non dichiara un membro di nome, ma si aspetta di ereditare che dal IFormObject:
using System;
namespace MyCompany.MyApplication.Domain.Forms
{
public interface IForm : IFormObject
{
bool ContainsRequiredFields();
MyCompany.MyApplication.Domain.Forms.Factories.IFormFieldFactory FormFieldFactory { get; }
MyCompany.MyApplication.Domain.Forms.Factories.IFormPageFactory FormPageFactory { get; }
string FriendlyName { get; set; }
System.Collections.Generic.List<IFormField> GetAllFields();
System.Collections.Generic.IEnumerable<DomainObjectPlaceholder> GetObjectPlaceholders();
System.Collections.Generic.IEnumerable<IFormField> GetRequiredFields();
System.Collections.Generic.IEnumerable<MyCompany.MyApplication.Models.Forms.FormObjectPlaceholder> GetRequiredObjectPlaceholders();
System.Collections.Generic.List<IFormSection> GetSectionsWithMultipliableOption();
MyCompany.MyApplication.BLL.IHighLevelFormUtilities HighLevelFormUtilities { get; }
int? MasterId { get; set; }
DomainObjectPlaceholder MasterObjectPlaceholder { get; set; }
MyCompany.MyApplication.Domain.Forms.Adapters.IObjectPlaceholderAdapter ObjectPlaceholderAdapter { get; }
MyCompany.MyApplication.Domain.Forms.Adapters.IObjectPlaceholderRelationshipAdapter ObjectPlaceholderRelationshipAdapter { get; }
System.Collections.Generic.List<IFormPage> Pages { get; set; }
MyCompany.MyApplication.Repository.IAppRepository AppRepo { get; set; }
int ZooFormId { get; }
MyCompany.MyApplication.BLL.IPocoUtils PocoUtils { get; }
void RemoveSectionWithoutChangingDatabase(int sectionId);
int? TopicId { get; set; }
DomainObjectPlaceholder TopicObjectPlaceholder { get; set; }
System.Collections.Generic.IEnumerable<FluentValidation.Results.ValidationResult> ValidationResults { get; set; }
}
}
Ed ecco l'IFormObject di interfaccia:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace MyCompany.MyApplication.Domain.Forms
{
public interface IFormObject
{
string Name { get; }
string LongName { get; }
Guid UniqueId { get; }
string Prefix { get; }
string IdPath { get; set; }
string IdPathWithPrefix { get; }
}
}
La domanda è: perché fa il Razor vista mi danno la seguente eccezione quando lo eseguo, poiché mi sarei aspettato che IForm ereditasse la sua proprietà Name da IFormObject?
Server Error in '/' Application.
The property MyCompany.MyApplication.Domain.Forms.IForm.Name could not be found.
Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.
Exception Details: System.ArgumentException: The property MyCompany.MyApplication.Domain.Forms.IForm.Name could not be found.
Source Error:
Line 24: </div>
Line 25: <div class="formFieldContainer">
Line 26: @Html.LabelFor(model => model.Form.Name, "Form title")
Line 27: @Html.EditorFor(model => model.Form.Name)
Line 28: @Html.ValidationMessageFor(model => model.Form.Name)
Source File: c:\Users\me\Documents\Visual Studio 2010\Projects\zooDBMain\zooDB\zooDB\Views\ZooForm\Edit.cshtml Line: 26
Stack Trace:
[ArgumentException: The property MyCompany.MyApplication.Domain.Forms.IForm.Name could not be found.]
System.Web.Mvc.AssociatedMetadataProvider.GetMetadataForProperty(Func`1 modelAccessor, Type containerType, String propertyName) +505385
System.Web.Mvc.ModelMetadata.GetMetadataFromProvider(Func`1 modelAccessor, Type modelType, String propertyName, Type containerType) +101
System.Web.Mvc.ModelMetadata.FromLambdaExpression(Expression`1 expression, ViewDataDictionary`1 viewData) +421
System.Web.Mvc.Html.LabelExtensions.LabelFor(HtmlHelper`1 html, Expression`1 expression, String labelText) +56
ASP._Page_Views_ZooForm_Edit_cshtml.Execute() in c:\Users\me\Documents\Visual Studio 2010\Projects\zooDBMain\zooDB\zooDB\Views\ZooForm\Edit.cshtml:26
System.Web.WebPages.WebPageBase.ExecutePageHierarchy() +272
System.Web.Mvc.WebViewPage.ExecutePageHierarchy() +81
System.Web.WebPages.StartPage.RunPage() +58
System.Web.WebPages.StartPage.ExecutePageHierarchy() +94
System.Web.WebPages.WebPageBase.ExecutePageHierarchy(WebPageContext pageContext, TextWriter writer, WebPageRenderingBase startPage) +173
System.Web.Mvc.RazorView.RenderView(ViewContext viewContext, TextWriter writer, Object instance) +220
System.Web.Mvc.BuildManagerCompiledView.Render(ViewContext viewContext, TextWriter writer) +115
System.Web.Mvc.ViewResultBase.ExecuteResult(ControllerContext context) +303
System.Web.Mvc.ControllerActionInvoker.InvokeActionResult(ControllerContext controllerContext, ActionResult actionResult) +13
System.Web.Mvc.<>c__DisplayClass1c.<InvokeActionResultWithFilters>b__19() +23
System.Web.Mvc.ControllerActionInvoker.InvokeActionResultFilter(IResultFilter filter, ResultExecutingContext preContext, Func`1 continuation) +260
System.Web.Mvc.<>c__DisplayClass1e.<InvokeActionResultWithFilters>b__1b() +19
System.Web.Mvc.ControllerActionInvoker.InvokeActionResultWithFilters(ControllerContext controllerContext, IList`1 filters, ActionResult actionResult) +177
System.Web.Mvc.ControllerActionInvoker.InvokeAction(ControllerContext controllerContext, String actionName) +343
System.Web.Mvc.Controller.ExecuteCore() +116
System.Web.Mvc.ControllerBase.Execute(RequestContext requestContext) +97
System.Web.Mvc.ControllerBase.System.Web.Mvc.IController.Execute(RequestContext requestContext) +10
System.Web.Mvc.<>c__DisplayClassb.<BeginProcessRequest>b__5() +37
System.Web.Mvc.Async.<>c__DisplayClass1.<MakeVoidDelegate>b__0() +21
System.Web.Mvc.Async.<>c__DisplayClass8`1.<BeginSynchronous>b__7(IAsyncResult _) +12
System.Web.Mvc.Async.WrappedAsyncResult`1.End() +62
System.Web.Mvc.<>c__DisplayClasse.<EndProcessRequest>b__d() +50
System.Web.Mvc.SecurityUtil.<GetCallInAppTrustThunk>b__0(Action f) +7
System.Web.Mvc.SecurityUtil.ProcessInApplicationTrust(Action action) +22
System.Web.Mvc.MvcHandler.EndProcessRequest(IAsyncResult asyncResult) +60
System.Web.Mvc.MvcHandler.System.Web.IHttpAsyncHandler.EndProcessRequest(IAsyncResult result) +9
System.Web.CallHandlerExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute() +8971485
System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously) +184
Version Information: Microsoft .NET Framework Version:4.0.30319; ASP.NET Version:4.0.30319.547
mi rendo conto che la mia gerarchia di ereditarietà è un po 'disordinato e che questo non è il codice più bello, ma io sono curioso di sapere il motivo per cui questo accade e quali sono le mie opzioni per il fissaggio. Non riesco a capire qualcosa su come l'ereditarietà dell'interfaccia funziona in C#?
Grazie Bryan.Deludente vedere che Microsoft non ha intenzione di risolverlo e (dal commento di Scott Hanselman sotto il primo dei tuoi link) che "Avere dei modelli basati sull'interfaccia non è qualcosa che incoraggiamo". Poiché in linea di principio qualsiasi classe può essere passata a una vista, inclusa una classe concreta con membri il cui tipo è un'interfaccia (come nel mio caso) il suo consiglio in realtà significa evitare le interfacce del tutto solo per essere sicuri, il che sembra una cattiva pratica . Utilizzerò la soluzione alternativa, che dovrebbe essere: @ Html.HiddenFor (m => (m come IFormObject) .Name) – Richard
@Richard: Bene, ora che ASP.NET MVC è open source, le cose potrebbero cambiare :) –