LINQ to SQL: OnValidate() and custom domain model classes - c#

Working through the NerdDinner Tutorial, I'm trying to figure out a good way to perform validation on properties that isn't dependent on a LINQ-to-SQL generated partial class. Here's some example code of what I've done so far:
public abstract class DomainEntity
{
public IEnumerable<ValidationError> ValidationErrors { get; private set; }
public bool Validate()
{
bool isValid = false;
if (this.ValidationErrors != null)
this.ValidationErrors = null;
this.ValidationErrors = this.GetValidationErrors();
if (this.ValidationErrors.Count() == 0)
isValid = true;
return isValid;
}
protected abstract IEnumerable<ValidationError> GetValidationErrors();
}
public partial class Email : DomainEntity
{
protected override IEnumerable<ValidationError> GetValidationErrors()
{
if (!this.ValidateAddress())
yield return new ValidationError("Address", DomainResources.EmailAddressValidationErrorMessage);
yield break;
}
partial void OnValidate(ChangeAction action)
{
bool isValid = this.Validate();
if (!isValid)
throw new InvalidEmailException(this);
}
private bool ValidateAddress()
{
// TODO: Use a regex to validate the email address.
return !string.IsNullOrEmpty(this.Address);
}
}
Where Email is a LINQ-to-SQL generated type based off an Email table. Since the Email table is but one of several entities related to a domain model class (say, "User"), the ideal is to create a "User" domain model class and use the Validation Application Block attributes to validate properties. In other words, I'd like to use this:
public class User
{
private Email emailEntity;
[EmailAddressValidator]
public string EmailAddress
{
get { return emailEntity.Address; }
set { emailEntity.Address = value; }
}
}
So that if I change my database schema, and the changes fall through my LINQ-to-SQL generated classes, I don't have these orphaned partial classes (like partial class Email). I also want the benefit from integrating the Validation Application Block attributes, so that I don't have to maintain a collection of regexes, as is done in the NerdDinner tutorial. Plus, User as a domain class is going to be the functional unit in the domain, not Email and other entities, for creating view models, rendering views, etc. However, there's no way to capture the Validation call without doing something like:
public abstract class DomainEntity
{
public event EventHandler Validation(object sender, EventArgs args);
protected void OnValidation()
{
if (this.Validate != null)
this.Validate(this, EventArgs.Empty);
}
}
public partial class Email
{
partial void OnValidate(ChangeAction action)
{
this.OnValidation();
}
}
And then having User hook into that event and handle all the validation within User. Would that even work well with the Validation Application Block? How to perform validation in aggregated domain classes like User in a sensible way?

Treat validation as a service rather than as a responsibility of the entity, this will let you separate implementation of the validation from the definition of what is valid and turn validation into an explicit operation rather than an implicit one ( managed by L2S ).
Have a look at fluent validation for .net ( http://www.codeplex.com/FluentValidation ) for a good implementation of this approach.

Related

Authorization code in business object or separate handler?

I have a business object that contains a collection of ACL items and I'm trying to decide whether to put the authorization code in the business object like this:
class Foo()
{
public IEnumerable<Permission> Permissions { get; set; }
public bool HasPermission(string username, FooOperation operation)
{
// check this Foo's Permissions collection and return the result
}
}
class FooHandler()
{
public void SomeOperation(Foo foo)
{
if(foo.HasPermission(username, FooPermission.SomeOperation))
{
// do some operation
}
}
}
Or in the object handler like this:
class Foo()
{
public IEnumerable<Permission> Permissions { get; set; }
}
class FooHandler()
{
public void SomeOperation(Foo foo)
{
if(SecurityManager.HasPermission(foo, username, FooPermission.SomeOperation))
{
// do some operation
}
}
}
class SecurityManager
{
public HasPermission(Foo foo, string username, FooPermission operation)
{
// check foo's Permissions collection and return the result
}
}
What are the pros and cons of each approach? Keeping in mind that Permissions collection will be public in either scenario b/c I'm using Entity Framework in my data layer to persist the business objects directly (I'm willing to change this down the road if necessary).
The second approach is nearest to a MVC Controller structure :)
but for your question, the best practice is to separate authorization from business logic, and you can implement access management as a separated method and call in any where that you need check access permissions. This is very equivalent to Authorize filter in a MVC controller.
Addition description:
I would like to remove ACL collection from business objects and retrieve them from the repository within the SecurityManager class.

How to implement auditing in the business layer

I'm trying to implement basic auditing for a system where users can login, change their passwords and emails etc.
The functions I want to audit are all in the business layer and I would like to create an Audit object that stores the datetime the function was called including the result.
I recently attended a conference and one of the sessions was on well-crafted web applications and I am trying to implement some of the ideas. Basically I am using an Enum to return the result of the function and use a switch statement to update the UI in that layer. The functions use an early return which doesn't leave any time for creating, setting and saving the audit.
My question is what approaches do others take when auditing business functions and what approach would you take if you had a function like mine (if you say ditch it I'll listen but i'll be grumpy).
The code looks a little like this:
function Login(string username, string password)
{
User user = repo.getUser(username, password);
if (user.failLogic1) { return failLogic1Enum; }
if (user.failLogic2) { return failLogic2Enum; }
if (user.failLogic3) { return failLogic3Enum; }
if (user.failLogic4) { return failLogic4Enum; }
user.AddAudit(new (Audit(AuditTypeEnum LoginSuccess));
user.Save();
return successEnum;
}
I could expand the if statements to create a new audit in each one but then the function starts to get messy. I could do the auditing in the UI layer in the switch statement but that seems wrong.
Is it really bad to stick it all in try catch with a finally and use the finally to create the Audit object and set it's information in there thus solving the early return problem? My impression is that a finally is for cleaning up not auditing.
My name is David, and I'm just trying to be a better code. Thanks.
I can't say I have used it, but this seems like a candidate for Aspect Oriented Programming. Basically, you can inject code in each method call for stuff like logging/auditing/etc in an automated fashion.
Separately, making a try/catch/finally block isn't ideal, but I would run a cost/benefit to see if it is worth it. If you can reasonably refactor the code cheaply so that you don't have to use it, do that. If the cost is exorbitant, I would make the try/finally. I think a lot of people get caught up in the "best solution", but time/money are always constraints, so do what "makes sense".
The issue with an enum is it isn't really extensible. If you add new components later, your Audit framework won't be able to handle the new events.
In our latest system using EF we created a basic POCO for our audit event in the entity namespace:
public class AuditEvent : EntityBase
{
public string Event { get; set; }
public virtual AppUser AppUser { get; set; }
public virtual AppUser AdminUser { get; set; }
public string Message{get;set;}
private DateTime _timestamp;
public DateTime Timestamp
{
get { return _timestamp == DateTime.MinValue ? DateTime.UtcNow : _timestamp; }
set { _timestamp = value; }
}
public virtual Company Company { get; set; }
// etc.
}
In our Task layer, we implemented an abstract base AuditEventTask:
internal abstract class AuditEventTask<TEntity>
{
internal readonly AuditEvent AuditEvent;
internal AuditEventTask()
{
AuditEvent = InitializeAuditEvent();
}
internal void Add(UnitOfWork unitOfWork)
{
if (unitOfWork == null)
{
throw new ArgumentNullException(Resources.UnitOfWorkRequired_Message);
}
new AuditEventRepository(unitOfWork).Add(AuditEvent);
}
private AuditEvent InitializeAuditEvent()
{
return new AuditEvent {Event = SetEvent(), Timestamp = DateTime.UtcNow};
}
internal abstract void Log(UnitOfWork unitOfWork, TEntity entity, string appUserName, string adminUserName);
protected abstract string SetEvent();
}
Log must be implemented to record the data associated with the event, and SetEvent is implemented to force the derived task to set it's event's type implicitly:
internal class EmailAuditEventTask : AuditEventTask<Email>
{
internal override void Log(UnitOfWork unitOfWork, Email email, string appUserName, string adminUserName)
{
AppUser appUser = new AppUserRepository(unitOfWork).Find(au => au.Email.Equals(appUserName, StringComparison.OrdinalIgnoreCase));
AuditEvent.AppUser = appUser;
AuditEvent.Company = appUser.Company;
AuditEvent.Message = email.EmailType;
Add(unitOfWork);
}
protected override string SetEvent()
{
return AuditEvent.SendEmail;
}
}
The hiccup here is the internal base task - the base task COULD be public so that later additions to the Task namespace could use it - but overall I think that gives you the idea.
When it comes to implementation, our other tasks determine when logging should occur, so in your case:
AuditEventTask task;
if (user.failLogic1) { task = new FailLogin1AuditEventTask(fail 1 params); }
if (user.failLogic2) { task = new FailLogin2AuditEventTask(fail 2 params); }
if (user.failLogic3) { task = new FailLogin3AuditEventTask(etc); }
if (user.failLogic4) { task = new FailLogin4AuditEventTask(etc); }
task.Log();
user.Save();

How can I use LazyLoading with WCF without causing circular references?

I am doing validation using DataAnnotation attributes on the Model classes, and the Model class is used for validation on both the Client and Server side of the application.
My problem is, I can't figure out how to lazy load my Model's properties without causing circular references
The libraries involved are:
WCF Service Library
Client-Side DataAccess Library
Models Library
Because the Models library is used on both the Client and Server side for data validation, I cannot reference the DataAccess library from within the Models library. Therefore, how can I setup lazy-loading?
For example, I have a ConsumerModel which has a property of PhoneNumbers which should be lazy loaded. How can I load the PhoneNumberModels from within the ConsumerModel without referencing the Client-Side DAL?
Client-side DAL:
using MyModels;
public class ConsumerDataAccess
{
public ConsumerModel GetConsumerById(int id)
{
ConsumerDTO dto = WCFService.GetConsumer(id);
return new ConsumerModel(dto);
}
}
ConsumerModel:
public class ConsumerModel
{
public ObservableCollection<PhoneNumberModel> _phoneNumbers;
public ObservableCollection<PhoneNumberModel> PhoneNumbers
{
get
{
if (_phoneNumbers == null)
{
// Can't reference DataAccess Library since that would cause a Circular Reference
}
}
}
}
What are some alternative ways I could make this architecture work?
I would prefer to keep Validation with the Models, and to use the models from both the Client and Server side for validation. I would also prefer to keep using DataAnnotation for Validation.
EDIT
Here's my final solution based on Lawrence Wenham's answer if anyone is interested. I ended up using a delegate instead of an event.
DAL:
public class ConsumerDataAccess
{
public ConsumerModel GetConsumerById(int id)
{
ConsumerDTO dto = WCFService.GetConsumer(id);
ConsumerModel rtnValue = new ConsumerModel(dto);
ConsumerModel.LazyLoadData = LazyLoadConsumerData;
return rtnValue;
}
}
private object LazyLoadConsumerData(string key, object args)
{
switch (key)
{
case "Phones":
return PhoneDataAccess.GetByConsumerId((int)args);
default:
return null;
}
}
Model Library:
public class ConsumerModel
{
public delegate object LazyLoadDataDelegate(string id, object args);
public LazyLoadDataDelegate LazyLoadData { get; set; }
public ObservableCollection<PhoneNumberModel> _phoneNumbers;
public ObservableCollection<PhoneNumberModel> PhoneNumbers
{
get
{
if (_phoneNumbers == null && LazyLoadData != null)
{
_phoneNumbers = (ObservableCollection<PhoneNumberModel>)
LazyLoadData("Phones", ConsumerId);
}
return _phoneNumbers;
}
}
}
One way might be to raise an event in the get {} of your Model classes properties, and then implement a lazy-loading manager on the client side that has a reference to your DAL. EG:
public class LazyLoadEventArgs: EventArgs
{
public object Data { get; set; }
public string PropertyName { get; set; }
public int Key { get; set; }
}
Then in your Model classes:
public event EventHandler<LazyLoadEventArgs> LazyLoadData;
public ObservableCollection<PhoneNumberModel> PhoneNumbers
{
get
{
if (_phoneNumbers == null)
{
LazyLoadEventArgs args = new LazyLoadEventArgs {
PropertyName = "PhoneNumbers",
Key = this.Id
};
LazyLoadData(this, args);
if (args.Data != null)
this._phoneNumbers = args.Data as ObservableCollection<PhoneNumberModel>;
}
return _phoneNumbers;
}
}
The handler for the LazyLoadData event would have the job of fetching the data from the client side's DAL, then storing it in the .Data property of LazyLoadEventArgs. EG:
private void Model_HandleLazyLoadData(object sender, LazyLoadEventArgs e)
{
switch (e.PropertyName)
{
case "PhoneNumbers":
e.Data = DAL.LoadPhoneNumbers(e.Key);
break;
...
}
}
Do not use "lazy loading" with WCF. Network communication is time expensive. If you plan to use PhoneNumbers your service should expose method which will return Customer with phone numbers. Other approach is using WCF Data Services which offers client side linq queries with ability to define eager loading by Expand method.
You should reduce service calls to minimum.
After reading again your question I don't understand why do you share model between service and client. Model is strictly client's feature. The only shared part should be DTOs.

Asp.Net MVC 2 - Bind a model's property to a different named value

Update (21st Sept 2016) - Thanks to Digbyswift for commenting that this solution still works in MVC5 also.
Update (30th April 2012) - Note to people stumbling across this question from searches etc - the accepted answer is not how I ended up doing this - but I left it accepted because it might have worked in some cases. My own answer contains the final solution I used, which is reusable and will apply to any project.
It's also confirmed to work in v3 and v4 of the MVC framework.
I have the following model type (the names of the class and its properties have been changed to protect their identities):
public class MyExampleModel
{
public string[] LongPropertyName { get; set; }
}
This property is then bound to a bunch (>150) of check boxes, where each one's input name is of course LongPropertyName.
The form submits to url with an HTTP GET, and say the user selects three of those checkboxes - the url will have the query string ?LongPropertyName=a&LongPropertyName=b&LongPropertyName=c
Big problem then is that if I select all (or even just over half!) the checkboxes, I exceed the maximum query string length enforced by the request filter on IIS!
I do not want to extend that - so I want a way to trim down this query string (I know I can just switch to a POST - but even so I still want to minimize the amount of fluff in the data sent by the client).
What I want to do is have the LongPropertyName bound to simply 'L' so the query string would become ?L=a&L=b&L=c but without changing the property name in code.
The type in question already has a custom model binder (deriving from DefaultModelBinder), but it's attached to its base class - so I don't want to put code in there for a derived class. All the property binding is currently performed by the standard DefaultModelBinder logic, which I know uses TypeDescriptors and Property Descriptors etc from System.ComponentModel.
I was kinda hoping that there might be an attribute I could apply to the property to make this work - is there? Or should I be looking at implementing ICustomTypeDescriptor?
In response to michaelalm's answer and request - here's what I've ended up doing. I've left the original answer ticked mainly out of courtesy since one of the solutions suggested by Nathan would have worked.
The output of this is a replacement for DefaultModelBinder class which you can either register globally (thereby allowing all model types to take advantage of aliasing) or selectively inherit for custom model binders.
It all starts, predictably with:
/// <summary>
/// Allows you to create aliases that can be used for model properties at
/// model binding time (i.e. when data comes in from a request).
///
/// The type needs to be using the DefaultModelBinderEx model binder in
/// order for this to work.
/// </summary>
[AttributeUsage(AttributeTargets.Property, AllowMultiple = true, Inherited = true)]
public class BindAliasAttribute : Attribute
{
public BindAliasAttribute(string alias)
{
//ommitted: parameter checking
Alias = alias;
}
public string Alias { get; private set; }
}
And then we get this class:
internal sealed class AliasedPropertyDescriptor : PropertyDescriptor
{
public PropertyDescriptor Inner { get; private set; }
public AliasedPropertyDescriptor(string alias, PropertyDescriptor inner)
: base(alias, null)
{
Inner = inner;
}
public override bool CanResetValue(object component)
{
return Inner.CanResetValue(component);
}
public override Type ComponentType
{
get { return Inner.ComponentType; }
}
public override object GetValue(object component)
{
return Inner.GetValue(component);
}
public override bool IsReadOnly
{
get { return Inner.IsReadOnly; }
}
public override Type PropertyType
{
get { return Inner.PropertyType; }
}
public override void ResetValue(object component)
{
Inner.ResetValue(component);
}
public override void SetValue(object component, object value)
{
Inner.SetValue(component, value);
}
public override bool ShouldSerializeValue(object component)
{
return Inner.ShouldSerializeValue(component);
}
}
This proxies a 'proper' PropertyDescriptor that is normally found by the DefaultModelBinder but presents its name as the alias.
Next we have the new model binder class:
UPDATED WITH #jsabrooke's suggestion below
public class DefaultModelBinderEx : DefaultModelBinder
{
protected override System.ComponentModel.PropertyDescriptorCollection
GetModelProperties(ControllerContext controllerContext,
ModelBindingContext bindingContext)
{
var toReturn = base.GetModelProperties(controllerContext, bindingContext);
List<PropertyDescriptor> additional = new List<PropertyDescriptor>();
//now look for any aliasable properties in here
foreach (var p in
this.GetTypeDescriptor(controllerContext, bindingContext)
.GetProperties().Cast<PropertyDescriptor>())
{
foreach (var attr in p.Attributes.OfType<BindAliasAttribute>())
{
additional.Add(new AliasedPropertyDescriptor(attr.Alias, p));
if (bindingContext.PropertyMetadata.ContainsKey(p.Name)
&& !string.Equals(p.Name, attr.Alias, StringComparison.OrdinalIgnoreCase)))
{
bindingContext.PropertyMetadata.Add(
attr.Alias,
bindingContext.PropertyMetadata[p.Name]);
}
}
}
return new PropertyDescriptorCollection
(toReturn.Cast<PropertyDescriptor>().Concat(additional).ToArray());
}
}
And, then technically, that's all there is to it. You can now register this DefaultModelBinderEx class as the default using the solution posted as the answer in this SO: Change the default model binder in asp.net MVC, or you can use it as a base for your own model binder.
Once you've selected your pattern for how you want the binder to kick in, you simply apply it to a model type as follows:
public class TestModelType
{
[BindAlias("LPN")]
//and you can add multiple aliases
[BindAlias("L")]
//.. ad infinitum
public string LongPropertyName { get; set; }
}
The reason I chose this code was because I wanted something that would work with custom type descriptors as well as being able to work with any type. Equally, I wanted the value provider system to be used still in sourcing the model property values. So I've changed the meta data that the DefaultModelBinder sees when it starts binding. It's a slightly more long-winded approach - but conceptually it's doing at the meta data level exactly what you want it to do.
One potentially interesting, and slightly annoying, side effect will be if the ValueProvider contains values for more than one alias, or an alias and the property by it's name. In this case, only one of the retrieved values will be used. Difficult to think of a way of merging them all in a type-safe way when you're just working with objects though. This is similar, though, to supplying a value in both a form post and query string - and I'm not sure exactly what MVC does in that scenario - but I don't think it's recommended practise.
Another problem is, of course, that you must not create an alias that equals another alias, or indeed the name of an actual property.
I like to apply my model binders, in general, using the CustomModelBinderAttribute class. The only problem with this can be if you need to derive from the model type and change it's binding behaviour - since the CustomModelBinderAttribute is inherited in the attribute search performed by MVC.
In my case this is okay, I'm developing a new site framework and am able to push new extensibility into my base binders using other mechanisms to satisfy these new types; but that won't be the case for everybody.
You can use the BindAttribute to accomplish this.
public ActionResult Submit([Bind(Prefix = "L")] string[] longPropertyName) {
}
Update
Since the 'longPropertyName' parameter is part of the model object, and not an independent parameter of the controller action, you have a couple of other choices.
You could keep the model and the property as independent parameters to your action and then manually merge the data together in the action method.
public ActionResult Submit(MyModel myModel, [Bind(Prefix = "L")] string[] longPropertyName) {
if(myModel != null) {
myModel.LongPropertyName = longPropertyName;
}
}
Another option would be implementing a custom Model Binder that performs the parameter value assignment (as above) manually, but that is most likely overkill. Here's an example of one, if you're interested: Flags Enumeration Model Binder.
would this be a solution similar to yours Andras? i hope you could post your answer as well.
controller method
public class MyPropertyBinder : DefaultModelBinder
{
protected override void BindProperty(ControllerContext controllerContext, ModelBindingContext bindingContext, System.ComponentModel.PropertyDescriptor propertyDescriptor)
{
base.BindProperty(controllerContext, bindingContext, propertyDescriptor);
for (int i = 0; i < propertyDescriptor.Attributes.Count; i++)
{
if (propertyDescriptor.Attributes[i].GetType() == typeof(BindingNameAttribute))
{
// set property value.
propertyDescriptor.SetValue(bindingContext.Model, controllerContext.HttpContext.Request.Form[(propertyDescriptor.Attributes[i] as BindingNameAttribute).Name]);
break;
}
}
}
}
Attribute
public class BindingNameAttribute : Attribute
{
public string Name { get; set; }
public BindingNameAttribute()
{
}
}
ViewModel
public class EmployeeViewModel
{
[BindingName(Name = "txtName")]
public string TestProperty
{
get;
set;
}
}
then to use the Binder in the controller
[HttpPost]
public ActionResult SaveEmployee(int Id, [ModelBinder(typeof(MyPropertyBinder))] EmployeeViewModel viewModel)
{
// do stuff here
}
the txtName form value should be set to the TestProperty.
This should probably be a shorter comment on Andras Zoltan's answer but don't have enough reputation, sorry.
Thanks for the solution, I've just used it and it still works great! However, some of my properties have an alias with the same name, but different case e.g.
[BindAlias("signature")]
public string Signature { get; set; }
These throw an error when the custom model binder tries to add the aliases to the
PropertyMetadata dictionary, as their main property name versions have already been added by the base model binder, and the model binding is case-insensitive.
To solve this, just do a case insensitive check -
replace
if (bindingContext.PropertyMetadata.ContainsKey(p.Name))
with
if (bindingContext.PropertyMetadata.ContainsKey(p.Name)
&& !string.Equals(p.Name, attr.Alias, StringComparison.OrdinalIgnoreCase))
So I've spent most of the day trying to figure out why I couldn't get this to work. Since I'm making my calls from a System.Web.Http.ApiController turns out that you can't use the DefaultPropertyBinder solution as mentioned above but instead must us an IModelBinder class.
the class that I've wound up writing to replace #AndreasZoltan's foundational work as written above is as follows:
using System.Reflection;
using System.Web;
using System.Web.Http.Controllers;
using System.Web.Http.ModelBinding;
using QueryStringAlias.Attributes;
namespace QueryStringAlias.ModelBinders
{
public class AliasModelBinder : IModelBinder
{
private bool TryAdd(PropertyInfo pi, NameValueCollection nvc, string key, ref object model)
{
if (nvc[key] != null)
{
try
{
pi.SetValue(model, Convert.ChangeType(nvc[key], pi.PropertyType));
return true;
}
catch (Exception e)
{
Debug.WriteLine($"Skipped: {pi.Name}\nReason: {e.Message}");
}
}
return false;
}
public bool BindModel(HttpActionContext actionContext, ModelBindingContext bindingContext)
{
Type bt = bindingContext.ModelType;
object model = Activator.CreateInstance(bt);
string QueryBody = actionContext.Request.Content.ReadAsStringAsync().Result;
NameValueCollection nvc = HttpUtility.ParseQueryString(QueryBody);
foreach (PropertyInfo pi in bt.GetProperties())
{
if (TryAdd(pi, nvc, pi.Name, ref model))
{
continue;
};
foreach (BindAliasAttribute cad in pi.GetCustomAttributes<BindAliasAttribute>())
{
if (TryAdd(pi, nvc, cad.Alias, ref model))
{
break;
}
}
}
bindingContext.Model = model;
return true;
}
}
}
In order to ensure that this runs as part of a WebAPI call you must also add config.BindParameter(typeof(TestModelType), new AliasModelBinder()); in the Regiser portion of your WebApiConfig.
If you are using this method, you also must remove [FromBody] from your method signature.
[HttpPost]
[Route("mytestendpoint")]
[System.Web.Mvc.ValidateAntiForgeryToken]
public async Task<MyApiCallResult> Signup(TestModelType tmt) // note that [FromBody] does not appear in the signature
{
// code happens here
}
Note that this work builds on the answer above, using the QueryStringAlias samples.
At the moment this would likely fail in the case where TestModelType had complex nested types. Ideally there are a few other things:
handle complex nested types robustly
enable an attribute on the class to activate the IModelBuilder as opposed to in the registration
enable the same IModelBuilder to work in both Controllers and ApiControllers
But for now I'm satisfied with this for my own needs. Hopefully someone finds this piece useful.

NHibernate + ASP.Net MVC + User Activity Feed

I am looking for the most appropriate way of dealing with a user activity feed on my social networking site. At the moment i have several activities which can appear on the news feed such as:
Users joins the site
User comments on a post
User adds a post to their favorites
User adds a new post to the site
Here is a simplified version of my domain objects at the moment:
public abstract class NewsItem : Entity, ITenantSpecific
{
public virtual Account Account { get; set; }
public virtual DateTime DateTime { get; set; }
// returns formatted news html string which gets
// overridden by inherted classes
public abstract string GetNewsHtml();
}
public class NewsItemJoiner : NewsItem
{
public virtual Account AccountJoined { get; set; }
public override string GetNewsHtml()
{
return "XXX has just joined our music network";
}
}
As you can see at the moment I have a property which must be overridden on each activity called GetNewsHtml. This isn't ideal as I don't believe my domain should be responsible for generating my HTML.
I have thought about using a partial view for each activity type and pass into it the NewsItem base class downcasted into the correct type.
However I am open to suggestions.
I have a similar issue but with different order types. I decided to define rendering at the view layer (web/controllers), not domain. You can do it this way:
public interface IRenderer<T> where T: NewsItem
{
string Render(T item);
}
public class NewsItemJoinerRenderer: IRenderer<NewsItemJoiner>
{
public string Render(T item)
{
return "XXX has just joined our music network";
}
}
public class NewsRendererFactory
{
public IRenderer<T> GetRenderer<T>()
{
return ServiceLocator.GetInstance<IRenderer<T>>();
}
}
Then you can pass NewsRendererFactory to controller. Perhaps there's a way to avoid ServiceLocator but for now I cannot tell.
Notice that this makes your architecture both configurable and pluggable if needed.
You can define additional render-related interfaces, add more properties to the IRenderer - for example, PartialName, etc, or have lambda filters on IRenderer that Factory uses to decide if this interface implementation is applicable for the passed (to GetRenderer("some-condition")) condition. A lot of things are possible.
If you don't want IoC containers (ServiceLocator), you can have its job done with simple switch() statement inside NewsRendererFactory.GetRenderer. This will isolate the logic inside single factory method, and you'll be able to replace it easily with true IoC once you are ready.
Update: how to get renderers.
If you don't use IoC, you do something like
typeof(IRenderer<>).Assembly.GetTypes().Where(x =>
x.IsGenericType &&
x.GetGenericTypeDefinition() == typeof(IRenderer<>) &&
x.GetGenericArguments().FirstOrDefault() == requestedTypeArguments)
Then you can select SingleOrDefault() or ToList() if you can handle multiple renderers.

Categories

Resources