I'm using MVC.NET 5.2.3 and try to post a model to a controller where the model contains several interfaces. This causes .NET to throw this exception:
Cannot create an instance of an interface
I understand that it's because I use interfaces in my model (ITelephone). My models are as such:
public class AddContactPersonForm
{
public ExternalContactDto ExternalContact { get; set; }
public OrganizationType OrganizationType { get; set; }
}
public class ExternalContactDto
{
public int? Id { get; set; }
public string Name { get; set; }
public string Title { get; set; }
public IList<ITelephone> TelephoneNumbers { get; set; }
}
public interface ITelephone
{
string TelephoneNumber { get; set; }
}
public class TelephoneDto : ITelephone
{
public string TelephoneNumber { get; set; }
}
It works fine if I use the TelephoneDto class instead of the ITelephone interface.
I understand that I need to use a ModelBinder, which is fine. But I really just want to say what kind of instance the modelbinder should create, instead of mapping the entire model manually.
The answer #jonathanconway gave in this question is close to what I want to do.
Custom model binder for a property
But I really would like to combine this with the simplicity of simply telling the defaultbinder what type to use for a specific interface. Sort of the same way you can use the KnownType-attribute. The defaultbinder obviously knows how to map the model as long as it knows which class it should create.
How can I tell the DefaultModelBinder what class it should use to deserialize the interface and then bind it? It currently crashes because the model that is posted (AddContactPersonForm) contains a "complex" model (ExternalContactDto) which has the interface ITelephone.
This is what I got so far.
public class ContactPersonController : Controller
{
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult AddContactPerson([ModelBinder(typeof(InterfaceModelBinder))] AddContactPersonForm addContactPersonForm)
{
// Do something with the model.
return View(addContactPersonForm);
}
}
public class InterfaceModelBinder : DefaultModelBinder
{
protected override void BindProperty(ControllerContext controllerContext, ModelBindingContext bindingContext,
PropertyDescriptor propertyDescriptor)
{
var propertyBinderAttribute = TryFindPropertyBinderAttribute(propertyDescriptor);
if (propertyBinderAttribute != null)
{
// Never occurs since the model is nested.
var type = propertyBinderAttribute.ActualType;
var model = Activator.CreateInstance(type);
bindingContext.ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => model, type);
base.BindProperty(controllerContext, bindingContext, propertyDescriptor);
return;
}
// Crashed here since because:
// Cannot create an instance of an interface. Object type 'NR.Delivery.Contract.Models.ITelephone'.
base.BindProperty(controllerContext, bindingContext, propertyDescriptor);
}
private InterfaceBinderAttribute TryFindPropertyBinderAttribute(PropertyDescriptor propertyDescriptor)
{
return propertyDescriptor.Attributes
.OfType<InterfaceBinderAttribute>()
.FirstOrDefault();
}
}
public class ExternalContactDto
{
public int? Id { get; set; }
public string Name { get; set; }
public string Title { get; set; }
[InterfaceBinder(typeof(List<TelephoneDto>))]
public IList<ITelephone> TelephoneNumbers { get; set; }
}
public class InterfaceBinderAttribute : Attribute
{
public Type ActualType { get; private set; }
public InterfaceBinderAttribute(Type actualType)
{
ActualType = actualType;
}
}
I think you have missed something:
The ViewModel is a non-visual class
All domain type concerns should be mapped to ViewModel classes. there should be no need for interfaces in your ViewModels as they should all be specific to the page/view that you are rendering.
If you plan to have the same data on multiple pages - You can still use classes - just use partial views, or child actions.
If you need more complex Model's then use inheritance or composition techniques .
Just use classes to avoid this issue altogether.
I think that you need to override a CreateModel method of model binder so you could create a model with correctly instantiated properties. You will have to use some reflection in order to dynamically create property values based on InterfaceBinderAttribute.ActualType property. So something like that should work:
protected override object CreateModel(ControllerContext controllerContext, ModelBindingContext bindingContext, Type modelType)
{
var model = Activator.CreateInstance(modelType);
var properties = modelType.GetProperties(BindingFlags.Public|BindingFlags.Instance);
foreach(var property in properties)
{
var attribute = property.Attributes.OfType<InterfaceBinderAttribute>().FirstOrDefault();
if(attribute != null)
{
var requiredType = (attribute as InterfaceBinderAttribute).ActualType;
property.SetValue(model, Activator.CreateInstance(requiredType ), null);
}
}
return model;
}
Related
I'm still fairly new to programming and have been tasked with creating a WebHook consumer that takes in a raw JSON string, parses the JSON into an object, which will be passed into a handler for processing. The JSON is coming in like this:
{
"id":"1",
"created_at":"2017-09-19T20:41:23.093Z",
"type":"person.created",
"object":{
"id":"person1",
"created_at":"2017-09-19T20:41:23.076Z",
"updated_at":"2017-09-19T20:41:23.076Z",
"firstname":"First",
...
}
}
The inner object can be any object so I thought this would be a great opportunity to use generics and built my class as follows:
public class WebHookModel<T> where T : class, new()
{
[JsonProperty(PropertyName = "id")]
public string Id { get; set; }
[JsonProperty(PropertyName = "created_at")]
public DateTime CreatedAt { get; set; }
[JsonProperty(PropertyName = "type")]
public string Type { get; set; }
[JsonProperty(PropertyName = "object")]
public T Object { get; set; }
[JsonIgnore]
public string WebHookAction
{
get
{
return string.IsNullOrEmpty(Type) ? string.Empty : Type.Split('.').Last();
}
}
}
Then created the following interface:
public interface IWebHookModelFactory<T> where T : class, new()
{
WebHookModel<T> GetWebHookModel(string type, string jsonPayload);
}
What I'm failing to understand is how am I supposed to implement the Factory class without knowing what the type is at compile time?
Playing around with the Model a bit, I changed it to an abstract class with an abstract T object so that it could be defined by a derived class.
public abstract class WebHookModel<T> where T : class, new()
{
[JsonProperty(PropertyName = "id")]
public string Id { get; set; }
[JsonProperty(PropertyName = "created_at")]
public DateTime CreatedAt { get; set; }
[JsonProperty(PropertyName = "type")]
public string Type { get; set; }
[JsonProperty(PropertyName = "object")]
public abstract T Object { get; set; }
[JsonIgnore]
public string WebHookAction
{
get
{
return string.IsNullOrEmpty(Type) ? string.Empty : Type.Split('.').Last();
}
}
}
public PersonWebHookModel : WebHookModel<Person>
{
public override Person Object { get; set; }
}
But I still run into the same issue of trying to implement an interface in which I don't know the type at runtime. From what I've found online, this is an example of covariance, but I haven't found any articles that explain how to resolve this issue. Is it best to skip generics and create a massive
case statement?
public interface IWebHookFactory<TModel, TJsonObject>
where TJsonObject : class, new()
where TModel : WebHookModel<TJsonObject>
{
TModel GetWebHookModel(string type, string jsonPayload);
}
I'm a bit partial to using the abstract class approach because it lets me define individual handlers based on which model I'm passing into my Service.
public interface IWebHookService<TModel, TJsonObject>
where TJsonObject : class, new()
where TModel : WebHookModel<TJsonObject>
{
void CompleteAction(TModel webHookModel);
}
public abstract class BaseWebhookService<TModel, TJsonObject> : IWebHookService<TModel, TJsonObject>
where TJsonObject : class, new()
where TModel : WebHookModel<TJsonObject>
{
public void CompleteAction(TModel webHookModel)
{
var self = this.GetType();
var bitWise = System.Reflection.BindingFlags.IgnoreCase
| System.Reflection.BindingFlags.Instance
| System.Reflection.BindingFlags.NonPublic;
var methodToCall = self.GetMethod(jsonObject.WebHookAction, bitWise);
methodToCall.Invoke(this, new[] { jsonObject });
}
protected abstract void Created(TModel webHookObject);
protected abstract void Updated(TModel webHookObject);
protected abstract void Destroyed(TModel webHookObject);
}
public class PersonWebHookService : BaseWebHookService<PersonWebHookModel, Person>
{
protected override void Created(PersonWebHookModel webHookModel)
{
throw new NotImplementedException();
}
protected override void Updated(PersonWebHookModel webHookModel)
{
throw new NotImplementedException();
}
protected override void Destroyed(PersonWebHookModel webHookModel)
{
throw new NotImplementedException();
}
}
Key points for the solution:
1. There needs to be some virtual call in there somewhere.
2. Somehow you need to map from your type tag in your JSON payload to your actual C# class.
IE, "person.created"," --> 'Person'.
If you control the serialization format, JSON.Net can inject its own type tag and do this for you. Assuming you can't go that route ...
So you'll need something like a Dictionary to contain the mapping.
Assuming your definitions is like:
abstract class WebhookPayload // Note this base class is not generic!
{
// Common base properties here
public abstract void DoWork();
}
abstract class PersonPayload : WebhookPayload
{
public override void DoWork()
{
// your derived impl here
}
}
And then you can deserialize like:
static Dictionary<string, Type> _map = new Dictionary<string, Type>
{
{ "person.created", typeof(PersonPayload)}
}; // Add more entries here
public static WebhookPayload Deserialize(string json)
{
// 1. only parse once!
var jobj = JObject.Parse(json);
// 2. get the c# type
var strType = jobj["type"].ToString();
Type type;
if (!_map.TryGetValue(strType, out type))
{
// Error! Unrecognized type
}
// 3. Now deserialize
var obj = (WebhookPayload) jobj.ToObject(type);
return obj;
}
I have a repository for a DocumentDb database. My documents all have a set of common properties so all documents implement the IDocumentEntity interface.
public interface IDocumentEntity {
[JsonProperty("id")]
Guid Id { get; set; }
[JsonProperty("documentClassification")]
DocumentClassification DocumentClassification { get; set; }
}
public class KnownDocument : IDocumentEntity {
[JsonProperty("id")]
Guid Id { get; set; }
[JsonProperty("documentClassification")]
DocumentClassification DocumentClassification { get; set; }
[JsonProperty("knownProperty")]
string KnownProperty { get; set; }
}
public class BaseDocumentRepository<T> where T : IDocumentEntity {
public Set(T entity) {
// ... stuff
}
}
This works fine with a KnownDocument where I know all of the properties. But, of course, what's great about a Document Db is that I don't need to know all of the properties (and in many cases I won't).
So my client submits something like this-
{unknownProperty1: 1, unknownProperty2: 2}
And I want to upsert this using my document repository.
public OtherDocumentService() {
_otherDocumentService = new OtherDocumentRepository();
}
public UpsertDocument(dynamic entity) {
entity.id = new Guid();
entity.documentClassification = DocumentClassification.Other;
_otherDocumentRepository.Set(entity);
}
But I get an InvalidCastException from dynamic to IDocumentEntity. I assume it's because of the extra properties that exist on the dynamic object but not on the IDocumentEntity interface?
What I'm trying to do is leave my document entities open to be dynamic, but rely on a few properties being there to maintain them.
Entity parameter passed to the UpsertDocument should explicitly implement IDocumentEntity in order do make the code works, it is not enough just have a Id property.
Some options:
1) Proxy may be applied:
public class ProxyDocumentEntity : IDocumentEntity
{
public dynamic Content { get; private set; }
public ProxyDocumentEntity(dynamic #content)
{
Content = #content;
}
public Guid Id
{
get { return Content.Id; }
set { Content.Id = value; }
}
}
... using
public void UpsertDocument(dynamic entity)
{
entity.Id = new Guid();
repo.Set(new ProxyDocumentEntity(entity));
}
The stored document will have nested Object property, which may be not acceptable
2)There is a lib https://github.com/ekonbenefits/impromptu-interface which creates a proxy dynamically
and does not make extra property like solution above.
Drawback will be in performance.
Technically it could be 2 methods:
public void UpsertDocument(IDocumentEntity entity){...}
public void UpsertDocument(dynamic entity){...}
so the first (fast) will work for the objects which implement IDocumentEntity and second(slow) for the rest of the objects.
But this is a speculation a bit , as I dunno the details of the whole code base of the project you have.
If you have some flexibility as to how to name those dynamic properties, you could stuff them into a Dictionary property on your object:
public Dictionary<string, dynamic> extra { get; set; }
I need some help transitioning from ValueInjecter to EmitMapper (I've decided so for performance reasons). My use case is one of the most common ones: mapping a Model object to a DTO, based on some rules.
One of this rules is: if a property's type is a subclass of DomainObject, then it should be mapped it to its correspondent DTO. With concrete types that's ok, but I also want it to work with abstract types. The problem is that I don't how to tell EmitMapper which DTO should be used, in a dynamic fashion.
In ValueInjecter code:
public bool IsDomainObjectAndTargetIsDto(ConventionInfo it)
{
return it.SourceProp.Value.IsNotNull()
&& typeof(DomainObject).IsAssignableFrom(it.SourceProp.Type)
&& it.TargetProp.Type.Name.EndsWith("DTO");
}
As all of my DTOs implements DTO<> interface, I thought I could use EmitMapper's DefaultMapConfig.ConvertGeneric method but I just can't figure out how.
Just for completeness, I include my current (not working) code:
public class ModelToDtoConventions()
{
public IMappingConfigurator GetConfig()
{
return new DefaultMapConfig()
.ConvertUsing<IdentificableObject, int>(o => o.Id)
.ConvertGeneric(
typeof (DomainObject),
typeof (DTO<>),
new DefaultCustomConverterProvider(
typeof (DomainObjectToDtoConverter<>)
)
);
}
}
public class DomainObjectToDtoConverter<TDomainObject>
{
public DTO<TDomainObject> Convert(TDomainObject from, object state)
{
return (DTO<TDomainObject>)this.CreateDtoFor(#from);
}
private object CreateDtoFor(object modelObject)
{
var modelType = modelObject.GetType();
var dtoInterface = typeof(DTO<>).MakeGenericType(modelType);
var dtoType = dtoInterface
.GetConcreteSubtypes()
.Single();
return Activator.CreateInstance(dtoType);
}
}
When I try to use this mapping on a test, I'm getting the following exception
'MyProject.WebApi.Test.Utils.DTOInjectorTest.Abstract_DTO_property_of_DTO_can_be_mapped_from_its_model' failed: System.ArgumentException : Cannot bind to the target method because its signature or security transparency is not compatible with that of the delegate type.
at System.Delegate.CreateDelegate(Type type, Object firstArgument, MethodInfo method, Boolean throwOnBindFailure)
at System.Delegate.CreateDelegate(Type type, Object firstArgument, MethodInfo method)
at EmitMapper.MappingConfiguration.MapConfigBaseImpl.GetGenericConverter(Type from, Type to)
at EmitMapper.MappingConfiguration.MapConfigBaseImpl.FilterOperations(Type from, Type to, IEnumerable`1 operations)
at EmitMapper.MappingConfiguration.DefaultMapConfig.GetMappingOperations(Type from, Type to)
at EmitMapper.EmitBuilders.MappingBuilder.BuildCopyImplMethod()
at EmitMapper.ObjectMapperManager.BuildObjectsMapper(String MapperTypeName, Type from, Type to, IMappingConfigurator mappingConfigurator)
at EmitMapper.ObjectMapperManager.GetMapperInt(Type from, Type to, IMappingConfigurator mappingConfigurator)
at EmitMapper.ObjectMapperManager.GetMapperImpl(Type from, Type to, IMappingConfigurator mappingConfigurator)
at MyProject.WebApi.Adapters.DTOInjector.Transform[TDestination](IMappingConfigurator config, Object source, TDestination destination) in c:\Users\faloi\Documents\GitHub\api\WebApi\Adapters\DTOInjector.cs:line 56
at MyProject.WebApi.Adapters.DTOInjector.CreateDto[TDTO](Object entity) in c:\Users\faloi\Documents\GitHub\api\WebApi\Adapters\DTOInjector.cs:line 47
at MyProject.WebApi.Test.Utils.DTOInjectorTest.Abstract_DTO_property_of_DTO_can_be_mapped_from_its_model() in c:\Users\faloi\Documents\GitHub\api\WebApi.Test\Utils\DTOInjectorTest.cs:line 334 c:\Users\faloi\Documents\GitHub\api\WebApi\Adapters\DTOInjector.cs 56
EDIT: this is an example of objects that I'd like to map.
//Domain objects
public class Game
{
public IEnumerable<Map> Maps { get; set; }
public Map MostPlayedMap { get; set; }
public Game()
{
this.Maps = new List<Map>();
}
}
public abstract class Map : DomainObject
{
public string Name { get; set; }
}
public class BombDefuseMap : Map
{
public Player BombHolder { get; set; }
}
public class HostageRescueMap : Map
{
public int QuantityOfHostages { get; set; }
}
//DTOs
public class GameDTO : DTOWithId<Game>
{
public List<MapDTO> Maps { get; set; }
public MapDTO MostPlayedMap { get; set; }
}
public abstract class MapDTO
{
public string Name { get; set; }
}
public class BombDefuseMapDTO : MapDTO, DTO<BombDefuseMap>
{
public int BombHolder { get; set; }
}
public class HostageRescueMapDTO : MapDTO, DTO<HostageRescueMap>
{
public int QuantityOfHostages { get; set; }
}
if you're concerned about performance have a look at this page:
http://valueinjecter.codeplex.com/wikipage?title=SmartConventionInjection
it's an injections that performs much better but you don't get the value in the matching algorithm,
most times you don't need it anyway
I've a model kind of complicated.
I have my UserViewModel which has several properties and two of them are HomePhone and WorkPhone. Both of type PhoneViewModel. In PhoneViewModel I have CountryCode, AreaCode and Number all strings. I want to make the CountryCode optional but AreaCode and Number mandatory.
This works great. My problem is that in the UserViewModel WorkPhone is mandatory, and HomePhone is not.
Is there anyway I can dissable Require attributs in PhoneViewModel by setting any attributes in HomeWork property?
I've tried this:
[ValidateInput(false)]
but it is only for classes and methods.
Code:
public class UserViewModel
{
[Required]
public string Name { get; set; }
public PhoneViewModel HomePhone { get; set; }
[Required]
public PhoneViewModel WorkPhone { get; set; }
}
public class PhoneViewModel
{
public string CountryCode { get; set; }
public string AreaCode { get; set; }
[Required]
public string Number { get; set; }
}
[UPDATED on 5/24/2012 to make the idea more clear]
I'm not sure this is the right approach but I think you can extend the concept and can create a more generic / reusable approach.
In ASP.NET MVC the validation happens at the binding stage. When you are posting a form to the server the DefaultModelBinder is the one that creates model instances from the request information and add the validation errors to the ModelStateDictionary.
In your case, as long as the binding happens with the HomePhone the validations will fire up and I think we can't do much about this by creating custom validation attributes or similar kind.
All I'm thinking is not to create model instance at all for HomePhone property when there are no values available in the form (the areacode, countrycode and number or empty), when we control the binding we control the validation, for that, we have to create a custom model binder.
In the custom model binder we are checking if the property is HomePhone and if the form contains any values for it's properties and if not we don't bind the property and the validations won't happen for HomePhone. Simply, the value of HomePhone will be null in the UserViewModel.
public class CustomModelBinder : DefaultModelBinder
{
protected override void BindProperty(ControllerContext controllerContext, ModelBindingContext bindingContext, PropertyDescriptor propertyDescriptor)
{
if (propertyDescriptor.Name == "HomePhone")
{
var form = controllerContext.HttpContext.Request.Form;
var countryCode = form["HomePhone.CountryCode"];
var areaCode = form["HomePhone.AreaCode"];
var number = form["HomePhone.Number"];
if (string.IsNullOrEmpty(countryCode) && string.IsNullOrEmpty(areaCode) && string.IsNullOrEmpty(number))
return;
}
base.BindProperty(controllerContext, bindingContext, propertyDescriptor);
}
}
Finally you have to register the custom model binder in global.asax.cs.
ModelBinders.Binders.Add(typeof(UserViewModel), new CustomModelBinder());
So now of you have an action that takes UserViewModel as parameter,
[HttpPost]
public Action Post(UserViewModel userViewModel)
{
}
Our custom model binder come into play and of form doesn't post any values for the areacode, countrycode and number for HomePhone, there won't be any validation errors and the userViewModel.HomePhone is null. If the form posts atleast any one of the value for those properties then the validation will happen for HomePhone as expected.
I've been using this amazing nuget that does dynamic annotations: ExpressiveAnnotations
It allows you to do things that weren't possible before such as
[AssertThat("ReturnDate >= Today()")]
public DateTime? ReturnDate { get; set; }
or even
public bool GoAbroad { get; set; }
[RequiredIf("GoAbroad == true")]
public string PassportNumber { get; set; }
Update: Compile annotations in a unit test to ensure no errors exist
As mentioned by #diego this might be intimidating to write code in a string, but the following is what I use to Unit Test all validations looking for compilation errors.
namespace UnitTest
{
public static class ExpressiveAnnotationTestHelpers
{
public static IEnumerable<ExpressiveAttribute> CompileExpressiveAttributes(this Type type)
{
var properties = type.GetProperties()
.Where(p => Attribute.IsDefined(p, typeof(ExpressiveAttribute)));
var attributes = new List<ExpressiveAttribute>();
foreach (var prop in properties)
{
var attribs = prop.GetCustomAttributes<ExpressiveAttribute>().ToList();
attribs.ForEach(x => x.Compile(prop.DeclaringType));
attributes.AddRange(attribs);
}
return attributes;
}
}
[TestClass]
public class ExpressiveAnnotationTests
{
[TestMethod]
public void CompileAnnotationsTest()
{
// ... or for all assemblies within current domain:
var compiled = Assembly.Load("NamespaceOfEntitiesWithExpressiveAnnotations").GetTypes()
.SelectMany(t => t.CompileExpressiveAttributes()).ToList();
Console.WriteLine($"Total entities using Expressive Annotations: {compiled.Count}");
foreach (var compileItem in compiled)
{
Console.WriteLine($"Expression: {compileItem.Expression}");
}
Assert.IsTrue(compiled.Count > 0);
}
}
}
I wouldn't go with the modelBinder; I'd use a custom ValidationAttribute:
public class UserViewModel
{
[Required]
public string Name { get; set; }
public HomePhoneViewModel HomePhone { get; set; }
public WorkPhoneViewModel WorkPhone { get; set; }
}
public class HomePhoneViewModel : PhoneViewModel
{
}
public class WorkPhoneViewModel : PhoneViewModel
{
}
public class PhoneViewModel
{
public string CountryCode { get; set; }
public string AreaCode { get; set; }
[CustomRequiredPhone]
public string Number { get; set; }
}
And then:
[AttributeUsage(AttributeTargets.Property]
public class CustomRequiredPhone : ValidationAttribute
{
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
ValidationResult validationResult = null;
// Check if Model is WorkphoneViewModel, if so, activate validation
if (validationContext.ObjectInstance.GetType() == typeof(WorkPhoneViewModel)
&& string.IsNullOrWhiteSpace((string)value) == true)
{
this.ErrorMessage = "Phone is required";
validationResult = new ValidationResult(this.ErrorMessage);
}
else
{
validationResult = ValidationResult.Success;
}
return validationResult;
}
}
If it is not clear, I'll provide an explanation but I think it's pretty self-explanatory.
Just some observation: the following code couse a problem if the binding is more than simple filed. I you have a case that in object have nested object it going to skip it and caouse that some filed not been binded in nested object.
Possible solution is
protected override void BindProperty(ControllerContext controllerContext, ModelBindingContext bindingContext, PropertyDescriptor propertyDescriptor)
{
if (!propertyDescriptor.Attributes.OfType<RequiredAttribute>().Any())
{
var form = controllerContext.HttpContext.Request.Form;
if (form.AllKeys.Where(k => k.StartsWith(string.Format(propertyDescriptor.Name, "."))).Count() > 0)
{
if (form.AllKeys.Where(k => k.StartsWith(string.Format(propertyDescriptor.Name, "."))).All(
k => string.IsNullOrWhiteSpace(form[k])))
return;
}
}
base.BindProperty(controllerContext, bindingContext, propertyDescriptor);
}
much thanks to Altaf Khatri
I have a customer hierarchy like so:
abstract class Customer {
public virtual string Name { get; set; }
}
class HighValueCustomer : Customer {
public virtual int MaxSpending { get; set; }
}
class SpecialCustomer : Customer {
public virtual string Award { get; set; }
}
When I retrieve a Customer, I would like to show on the web form the properties to edit/modify. Currently, I use if statements to find the child customer type and show the specialized properties. Is there a design pattern (visitor?) or better way so I can avoid the "if" statements in presentation layer? How do you do it?
Further information: This is an asp.net website with nHibernate backend. Each customer type has its own user control on the page that I would like to load automatically given the customer type.
Can you use reflection to get the list of properties specific to an subclass (instance)? (Less error-prone.)
If not, create a (virtual) method which returns the special properties. (More error prone!)
For an example of the latter:
abstract class Customer {
public virtual string Name { get; set; }
public virtual IDictionary<string, object> GetProperties()
{
var ret = new Dictionary<string, object>();
ret["Name"] = Name;
return ret;
}
}
class HighValueCustomer : Customer {
public virtual int MaxSpending { get; set; }
public override IDictionary<string, object> GetProperties()
{
var ret = base.GetProperties();
ret["Max spending"] = MaxSpending;
return ret;
}
}
class SpecialCustomer : Customer {
public virtual string Award { get; set; }
public override IDictionary<string, object> GetProperties()
{
var ret = base.GetProperties();
ret["Award"] = Award;
return ret;
}
}
You probably want to create sections (fieldsets?) on your Web page, anyway, so if would come into play there, making this extra coding kinda annoying and useless.
I think a cleaner organization would be to have a parallel hierarchy of display controls or formats. Maybe use something like the Abstract Factory Pattern to create both the instance of Customer and of CustomerForm at the same time. Display the returned CustomerForm instance, which would know about the extra properties and how to display and edit them.
new:
interface CustomerEdit
{
void Display();
}
edit:
abstract class Customer {
protected CustomerEdit customerEdit; // customers have an object which allows for edit
public virtual string Name { get; set; }
public void Display() { customerEdit.Display(); } // allow the CustomerEdit implementor to display the UI elements
}
// Set customerEdit in constructor, tie with "this"
class HighValueCustomer : Customer {
public virtual int MaxSpending { get; set; }
}
// Set customerEdit in constructor, tie with "this"
class SpecialCustomer : Customer {
public virtual string Award { get; set; }
}
usage:
Customer whichCouldItBe = GetSomeCustomer();
whichCouldItBe.Display(); // shows UI depeneding on the concrete type
Have you tried something like this:
public class Customer<T>
where T : Customer<T>
{
private T subClass;
public IDictionary<string, object> GetProperties()
{
return subClass.GetProperties();
}
}
With a subclass of:
public class FinancialCustomer : Customer<FinancialCustomer>
{
}
This is off the top of my head so might not work. I've seen this type of code in CSLA.NET.
Here's the link to the CSLA.NET class called BusinessBase.cs which has a similar definition to what I've given above.