Parsing The Payload
So this week we got to start working a brand spanking new MVC project. So far we’re leveraging Castle Windsor, NHibernate, Fluent Nhibernate, and kind of running Linq to NHibernate. It’s amazing how quickly you can get a project up and running in such a short amount of time. (BTW, Fluent NHibernate rocks!) When you’re building off the trunk of these projects, it’s almost like the contributors to all these great projects are extended members of the team. Thank you all!
Moving on… One of the things that are cool, but also slightly annoying, is how the MVC framework parses out items from the http payload to populate any input arguments on controller actions.
It’s great how it just works, but it’s a little annoying if it’s under test and you have to add more fields, or remove fields from a form, then you have to go update the signature of the action then go update the test…. yada yada The changes just ripple down…
So one thing we tried out this week was to create a payload parser. What this guy does is take a DTO parse out the values for each of the properties on the DTO from the current requests payload and fill it. This makes it easy to package up the form parameters in a nicely packaged DTO and fire it off down to a service layer to do some work.
So instead of declaring an action method on a controller that looks like this, where the signature would have to change based on what fields are submitted on a form:
viewresult register_new_account(string user_name, string first_name,string last_name)
we can write this…
public viewresult register_new_account() { var accountsubmissiondto = parser.mapfrompayloadto<accountsubmissiondto>(); var validationresult = task.validate(accountsubmissiondto); if (validationresult.isvalid) { task.submit(accountsubmissiondto); return view("Success", accountsubmissiondto); } return view("Index", validationresult.brokenrules); }
this better allows us to adhere to the ocp. if we need to include additional fields on the form, we can add them to the form as long as the control name is the same as the name of the property on the dto that it will be bound to. the implementation of the payload parser is quite primitive for now, but at the moment it’s all that we needed.
first up the specs… simple enough, for now!
public class when_parsing_the_values_from_the_current_request_to_populate_a_dto : context_spec<ipayloadparser> { [test] public void should_return_a_fully_populated_dto() { result.name.should_be_equal_to("adam"); result.age.should_be_equal_to(15); result.birthdate.should_be_equal_to(new datetime(1982, 11, 25)); result.id.should_be_equal_to(1); } protected override ipayloadparser undertheseconditions() { var current_request = dependency<iwebrequest>(); var payload = new namevaluecollection(); payload["Name"] = "adam"; payload["Age"] = "15"; payload["Birthdate"] = new datetime(1982, 11, 25).tostring(); payload["Id"] = "1"; current_request.setup_result_for(r => r.payload).return(payload); return new payloadparser(current_request); } protected override void becauseof() { result = sut.mapfrompayloadto<somedto>(); } private somedto result; } public class when_parsing_values_from_the_request_that_is_missing_values_for_a_properties_on_the_dto : context_spec<ipayloadparser> { private accountsubmissiondto result; [test] public void it_should_supress_any_errors() { result.lastname.should_be_null(); result.emailaddress.should_be_null(); } protected override ipayloadparser undertheseconditions() { var current_request = dependency<iwebrequest>(); var payload = new namevaluecollection(); payload["FirstName"] = "Joel"; current_request.setup_result_for(x => x.payload).return(payload); return new payloadparser(current_request); } protected override void becauseof() { result = sut.mapfrompayloadto<accountsubmissiondto>(); } }
public class somedto { public long id { get; set; } public string name { get; set; } public int age { get; set; } public datetime birthdate { get; set; } }
the current implementation:</p>
public interface IPayloadParser { TypeToProduce MapFromPayloadTo<TypeToProduce>() where TypeToProduce : new(); } public class PayloadParser : IPayloadParser { private readonly IWebRequest current_request; public PayloadParser(IWebRequest current_request) { this.current_request = current_request; } public TypeToProduce MapFromPayloadTo<TypeToProduce>() where TypeToProduce : new() { var dto = new TypeToProduce(); foreach (var propertyInfo in typeof (TypeToProduce).GetProperties()) { var value = Convert.ChangeType(current_request.Payload[propertyInfo.Name], propertyInfo.PropertyType); propertyInfo.SetValue(dto, value, null); } return dto; } }