views:

1255

answers:

6

I have a website where I allow users to create new Part records. I'm trying to figure out the best way to validate specific fields for uniqueness. I want to make sure that somebody doesn't try to add a Part with PartNumber 1234 if that PartNumber already exists on a different Part.

The Web Application is using Asp.net MVC with fluent nHibernate for mapping my objects to the database. I'm using Castle validation on my view models for things like ValidateNonEmpty, ValidateRange, etc. Should I use the ValidateSelf method to query the repository to see if that part number already exists? Something doesn't feel right about using my Repository on the ViewModel.

Would it be better for me to place that logic on the controller action? That doesn't seem right because I expect my ViewModel to already be Validated at the point (during ModelBind).

Or maybe its none of the above. Thanks for any help on this one.

UPDATE Ok, not sure if this will help, but here is what my Save action looks like for a typical Create Action in my project:

public ActionResult Create(PartViewModel viewModel)
{
 //I think I'd like to know if its Valid by this point, not on _repository.Save
 if(ModelState.IsValid)
 {
    try
    {
        var part = _partCreateViewModelMap.MapToEntity(viewModel);

        _repository.Save(part);
        return Redirect("~/Part/Details/" + part.Id);
    }
    catch (Exception e)
    {
        // skip on down...
    }
 }

 // return view to edit 
 return View(viewModel);
}
A: 

I have no answer for your question but you can check sharparchitecture.net site. It contains some best practives for asp.net mvc and nhibernate. Also I can recommend you to check xval project and tutorials about Validating with Data Annotation Validators

hodzanassredin
@hodzanassredin, thanks - I do use the xVal framework. This just sits between my server side validation and my client validation framework (jquery). xVal doesn't really have anything by way of remote validation though, so I may look into Adrian Grigore's article about that.
shanabus
+1  A: 

I have been asked this question many times. My friends were worried about whether they can perform data access from the validator code. The answer is simple. If you need to do this, you should do it. Usually we need to do such checks at each level of abstraction. And after all checks you should be ready to catch an exception, caused by unique constraint violation.

serge_bg
Thanks for your response. I'm not trying to catch an exception though, I think I want to see if it exists before I try to commit it. That would be a validation error causing the ModelStat.IsValid to be false - not an exception.
shanabus
The validation does not guarantee successful completion of the operation.
serge_bg
A: 

I have found the solution that works for me is to

1.) Ask if the entity is valid to execute your validation work.
2.) After this is complete you should have something on your object to show it's valid or not (in my case I use a CSLA like concept of "broken rules").
3.) If you have something like this you can verify the object is valid before NHibernate tries to persist it as shown below.

The only issue with this approach is that you do need to implement an interface on each entity requiring validation. If you can live with this it will stop NHibernate from persisting the changes of an object that's not valid according to your rules.

using System;
using NHibernate;
using NHibernate.Event;
using Validation.Entities.Interfaces;
using Persistence.SessionBuilder;

namespace Persistence.Validation
{
    public class ValidationEventListener : IPreInsertEventListener, IPreUpdateEventListener
    {

        public bool OnPreInsert(NHibernate.Event.PreInsertEvent @event)
        {
            var entityToInsert = @event.Entity as IBusinessBase;

            if (entityToInsert != null)
            {
                if (entityToInsert.BrokenRules != null)
                {
                    RollbackTransactionBecauseTheEntityHasBrokenRules();
                }
            }

            return false;
        }

        public bool OnPreUpdate(NHibernate.Event.PreUpdateEvent @event)
        {
            var entityToUpdate = @event.Entity as IBusinessBase;

            if (entityToUpdate != null)
            {
                if (entityToUpdate.BrokenRules != null)
                {
                    RollbackTransactionBecauseTheEntityHasBrokenRules();
                }
            }

            return false;
        }

        private void RollbackTransactionBecauseTheEntityHasBrokenRules()
        {
            try
            {
                ISession session = SessionBuilderFactory.GetBuilder().CurrentSession;

                if (session != null)
                {
                    session.Transaction.Rollback();
                }
            }
            catch (Exception ex)
            {
                //this will force a rollback if we don't have a session bound to the current context 
                throw new NotImplementedException();
            }
        }
    }
}
Toran Billups
@Toran Billups, Thank you for your answer. I don't think this solves the problem only because my question is more about when/where to validate the "BrokenRules" than it is about how to handle it on PreInsert or PreUpdate. I think I want to know if its going to be a valid commit before I try to commit it. Or is that the wrong way to look at it? Check out my update please.
shanabus
Understood, in that case I would recommend that you move all the code in your above controller to a service, or facade layer that would be in charge of asking the model if the state is valid and then doing the save work (if it's valid). The reason my code is still required is the NHibernate will persist (or try to persist) the changes regardless of you explicitly calling "_repository.Save(part)" - and because of this, some type of validation event listener is needed to stop this persistence from happening.
Toran Billups
@Toran, thanks again - this codes isn't required if I can validate the ModelState first. You can see by my update that I test ModelState.IsValid before trying to save it. Maybe the solution is to call this new ValidationService type layer and pass in the repository from the controller. That would allow for dependency injection and I could do it before the "if (ModelState.IsValid)" so validation errors could still bubble up to the viewModel. Maybe i pass in the ModelState or ErrorSummary to the new service as well.
shanabus
+1  A: 

If you define a unique constraint within the database, then why not delegate the responsibility to check for whether a unique value already exists to the database? Using NHibernate, you can use the NHibernate.Exceptions.ISQLExceptionConverter interface to capture and transform known errors relating to constraint violations. You can also use NHibernate.Exceptions.IViolatedConstraintNameExtracter implementers (see NHibernate.Exceptions.TemplatedViolatedConstraintNameExtracter) to get at the grubby details of your database exception, and transform it into a user-friendly message, repackage as a validation exception of your chosing and catch it in the relevant controller.

Example of a quick, very specific quick and dirty exception converter from one of my projects:


Imports NHibernate
Imports NHibernate.Exceptions
Imports System.Data.SqlClient
Imports System.Data.Common

Namespace NHibernate

    Public Class ConstraintViolationExceptionConverter
        Implements ISQLExceptionConverter

        Public Function Convert(ByVal adoExceptionContextInfo As Global.NHibernate.Exceptions.AdoExceptionContextInfo) As System.Exception Implements Global.NHibernate.Exceptions.ISQLExceptionConverter.Convert

            Dim dbEx As DbException = ADOExceptionHelper.ExtractDbException(adoExceptionContextInfo.SqlException)

            If TypeOf dbEx Is SqlException Then
                Dim sqlError As SqlException = DirectCast(dbEx, SqlException)

                Select Case sqlError.Number
                    Case 547
                        Return New ConstraintViolationException(adoExceptionContextInfo.Message, adoExceptionContextInfo.SqlException)

                End Select

            End If

            Return SQLStateConverter.HandledNonSpecificException(adoExceptionContextInfo.SqlException, adoExceptionContextInfo.Message, adoExceptionContextInfo.Sql)

        End Function


    End Class

End Namespace

Configured through the web.config/nhibernate-configuration/session-factory property element:


<property name="sql_exception_converter">csl.NHibernate.ConstraintViolationExceptionConverter, csl</property>

Edit: Should probably mention that the converter interface has changed in recent versions of NHibernate, the interface from this example is from NHibernate.dll v2.1.0.4000

Rabid
Thank you @Rabid but I'm not sure how this applies to my situation. If nHibernate provides me with a way to create unique columns outside of the Primary Key for the Guid then that would be great to know, but I'm definitely trying to avoid bouncing saves off the Database - that really isn't validation, its error handling
shanabus
+1  A: 

I typically put a Service layer between my controllers and repositories.
The service layer would then handle the validation and calls to the repository.

Then, if there's a validation error in the service layer, I throw a custom exception, catch it in the controller, and inject the errors in to the model state.

CitizenBane
@CitizenBane, thanks for your answer. I'm specifically looking to retain the ability to add Error Message to the ErrorSummary of the Castle.Components.Validator. It feels like I should do that before it hits the controller so that I immediately return the view-model for edit
shanabus
@shanabus I'm not familiar with the Castle.Components.Validator, but after doing a quick google search, I found this: http://altinoren.com/PermaLink,guid,bca0aba7-dbaa-4ccf-bcf3-4d76efab1a40.aspx HTH
CitizenBane
@CitezenBane, I've already got Castle wired up with a ValidationRunner and attributes on my view-model properties. My questions is about the best way to validate unique data in the repository. This link doesn't address that at all.
shanabus
A: 

I would say this matters on your architecture. With MVC apps that I have done in the past we abstract away the domain stuff away from the web stuff and naturally we use dependency injection to avoid hard dependencies.

When it comes to validating the model when you are in the act of binding it, yes you could easily use the service, repository, or whatever you have next in your architecture in a ValidateSelf method. I think the question rises of what about that dependency.

If I remember correctly you can create your own custom binder that will use your dependency injection framework to plug-in any services your model needs for validation when you create it, call MVC's default binder to fill in the object, then call into Castle Validation's framework to do the validation. This isn't a fully thought solution, but hopefully it provokes some ideas.

Sean Copenhaver
Thanks @Sean Copenhaver, this does provoke some ideas. I didn't want to look into a custom binder just because each Domain Entity has a different property that will be used to validate uniqueness. Maybe I combine a custom binder with a custom attribute like [MustBeUnique] that way the binder knows what to process.
shanabus