Archive for the ‘ASP.NET MVC’ Category
For most people out there using the current Validation Frameworks (DataAnnotations, Nhibernate Validation) are more than enough. But for some, like me, I need mine to be “Context Aware”.
What do I mean with “Context Aware”? Well lets say, for instance, that you are phoning your bank for an insurance policy. The first thing they need is to obtain just enough info to get you in their database so that they can take the application further. Once they have you, then they forward the application where underwriting, risk calculation etc can take place. So the application goes through quite a few states. We could say it has a preliminary, draft, final etc… state at each given moment. The validation and business rules might be completely different depending on what state the agreement is in.
Three more requirements had to be satisfied before I started to plan a prototype.
1. Needs to be VERY simple to use
2. Needs to stay out of my POCO objects (no attributes!)
3. Validation rules are just a subset of business rules. I want to be able to use the validation framework for complex business rules and simple validation and treat them as one layer.
After a few days NBouncer was born. This is just a prototype and more feedback is needed to justify investing more time into. I know there are “Context Aware” rule frameworks out there, but I need mine to be VERY SIMPLE.
Enough rambling, lets see some code!
Lets say we have a Person object, an Agreement object and a Hobby object: (note how clean these are, this is how they will stay!)
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
public IList<Hobby> Hobbies = new List<Hobby>();
}
public class Agreement
{
public AgreementState State { get; set; }
}
public class Hobby
{
public string Name { get; set; }
}
The first example is to send a Warning message if the execution action is “Validate”, but to send an Error if the user is trying to “Persist” the person. The only thing we need to do is create a PersonRules class and set an attribute specifying it’s a “RuleProvider” for the “Person” class…
[RuleProviderFor(typeof (Person))]
public class PersonRules : RuleProvider<Person>
{
public PersonRules(RuleContext context)
: base(context)
{
}
protected override void RegisterRules()
{
CreateNameValidationRules();
}
private void CreateNameValidationRules()
{
switch (Context.ExecutionAction)
{
case ExecutionAction.Validate:
RegisterRule(x => string.IsNullOrEmpty(x.Name),
CreateWarning(ErrorMessages.NameRequired, "Name"));
break;
case ExecutionAction.Persist:
RegisterRule(x => string.IsNullOrEmpty(x.Name),
CreateError(ErrorMessages.NameRequired, "Name"));
break;
}
}
}
A more complicated example is when we have custom “Actions” and the rules are dependent of other classes…
[RuleProviderFor(typeof (Person))]
public class PersonRules : RuleProvider<Person>
{
public PersonRules(RuleContext context)
: base(context)
{
}
protected override void RegisterRules()
{
var data = Context.ResolveData<Agreement>();
CreateNameValidationRules();
CreateAgeValidationRules(data);
CreateHobbyValidationRules(data);
}
private void CreateHobbyValidationRules(Agreement agreement)
{
if (Context.CustomAction == CustomActions.UnexpectedExit)
{
RegisterRule(x => x.Hobbies.Count == 0,
CreateError(ErrorMessages.HobbiesCannotBeEmpty, "Hobby"));
}
else
{
switch (Context.ExecutionAction)
{
case ExecutionAction.Persist:
if (agreement.State == AgreementState.Draft)
{
RegisterRule(x => x.Hobbies.Count == 0,
CreateWarning(ErrorMessages.HobbiesCannotBeEmpty, "Hobby"));
}
else if(agreement.State == AgreementState.Final)
{
RegisterRule(x => x.Hobbies.Count == 0,
CreateError(ErrorMessages.HobbiesCannotBeEmpty, "Hobby"));
}
break;
case ExecutionAction.Release:
RegisterRule(x => x.Hobbies.Count == 0,
CreateError(ErrorMessages.HobbiesCannotBeEmpty, "Hobby"));
break;
}
}
}
private void CreateAgeValidationRules(Agreement agreement)
{
switch (Context.ExecutionAction)
{
case ExecutionAction.Persist:
if (agreement.State == AgreementState.Draft)
{
RegisterRule(x => x.Age < 18,
CreateWarning(ErrorMessages.AgeCannotBeLessThan18, "Age"));
}
else if(agreement.State == AgreementState.Final)
{
RegisterRule(x => x.Age < 18,
CreateError(ErrorMessages.AgeCannotBeLessThan18, "Age"));
}
break;
case ExecuteAction.Validate:
RegisterRule(x => x.Age < 18,
CreateWarning(ErrorMessages.AgeCannotBeLessThan18, "Age"));
break;
}
}
}
The only thing left to do is to see how we validate…
public ActionResult EditPerson(Person person)
{
var agreement = new Agreement {State = AgreementState.Draft};
var ruleContext = new RuleContext(ExecutionAction.Persist);
ruleContext.RegisterData(agreement);
var rules = person.GetRuleProvider(ruleContext);
if(rules.Validate())
{
rules.ValidationResult.ForEach(x => ModelState.AddModelError(x.Message, x.TargetProperty);
return View(person);
}
Service.CreateAgreement(person, agreement);
return View(person);
}
As I said before, this is just a prototype and feedback would be appreciated!
This weekend I looked into creating facebook applications from a .NET perspective. It turns out a quick search presents loads of support starting with the Facebook Developer Toolkit (Update: Just heard version 3 was released yesterday which gives silverlight and mvc support, wow what a coincidence?) and LINQ2Facebook (Fql).
I decided to do some research and ended up created a Facebook application framework using ASP.NET MVC, Silverlight, LINQ2Facebook. I also used StoryQ to POC their BDD framework and give the community a chance to see its power.
I have created a codeplex project with the source code here:
Facebook MVC BDD Silverlight Framework
Source code sample:
public ActionResult Index()
{
if (_service.TryAuthenticating())
{
user user = _service.GetCurrentUser();
var silverlightViewModel = new SilverlightViewModel
{
ApplicatonName = "TestApp",
Width = "400",
Height = "400"
};
silverlightViewModel.Params.Add("Name", user.name);
silverlightViewModel.Params.Add("Picture", user.pic_big);
return View(silverlightViewModel);
}
throw new AuthenticationException("Not allowed!");
}
[Test]
public void IndexActionShouldReturnViewModelWithCurrentLoggedInUser()
{
var story = new Story("Index action should return a new silverlight view model");
story.AsA("facebook application home page")
.IWant("to receive a new silverlight view model")
.SoThat("I can send it to the silverlight applicatioin")
.WithScenario("Get silverlight view model")
.Given(() => Steps.TheUserIsLoggedInWithUsername("Joe Blogs"))
.When(() => Steps.TheUserEntersTheHomePage())
.Then(() => Steps.EnsureTheCurrentLoggedInUserDetailsIsReceivedInTheViewModel());
story.Assert();
}
producing output:
Story: Index action should return a new silverlight view model
As a facebook application home page
I want to receive a new silverlight view model
So that I can send it to the silverlight applicatioin
Scenario 1: Get silverlight view model
Given The user is logged in with username (Joe Blogs) Passed
When The user enters the home page Passed
Then Ensure the current logged in user details is received in the view model Passed
As I was developing a CMS system for a client, we had a very unique scenario (well maybe not that unique) where we had custom “parts” which you could add to a page. The “parts” were highly customisable, thus you could have 3 instances of the same “part” on one page. The html and javascript rendered by the part would conflict with each other, because they had the same id’s and registered events.
I needed something similar to the old ASP.NET Script manager and the way that old ASP.NET used the “clientId” instead of the “htmlId” when dealing with html objects. The “clientId” was a generated unique key that prevented duplicated user controls conflicting with one another.
I found quite a few implementations of ScriptManager (http://pietschsoft.com/post/2009/08/13/Simple-ScriptManager-for-ASPNET-MVC.aspx, http://aspmvccombine.codeplex.com/) for MVC, but none of them were solving my unique “htmlId” problem. If you are looking for a ScriptManager for MVC, then the two solutions I provided would be more than enough. If, however, you are in a similar situation as I am with the html controls, please read on…
I decided to combine knowledge obtained from the two ScriptManager implementations, and create my own light-weight solution addressing both problems.
Because I wanted the usage of this to be very simple, I created a fluent interface which I can use in all my parts…
This is what the end result looks like:
<% Html.ScriptManager()
.CreateClientScript(GetUniqueId())
.AddParam("SelectId", GetUniqueId("selectList"))
.AddParam("SourceUrl", Url.Action("Index", "Person"))
.Ready(() => {%>
$.getJSON(params.SourceUrl, function(result) {
$.populate_combobox(result, params.SelectId);
});
<%});%>
<% Html.ScriptManager().AddStaticMethod("populate_combobox", () =>{ %>
jQuery.populate_combobox = function(data, selectId) {
$.each(data, function() {
var option = new Option(this.text, this.value);
var dropdownList = $(selectId)[0];
if ($.browser.msie) {
dropdownList.add(option);
} else {
dropdownList.add(option, null);
}
});
}
<%});%>
<div id="<%=GetUniqueId() %>_part">
<select id="<%=GetUniqueId("selectList") %>"></select>
</div>
My “GetUniqueId()” function returns a combination of the PartId, PageId, but you can replace this with anything you fancy.
The javascript that gets generated by this is:
var ContentPart_AboutPage_82 = {
Init: function(params) {
$.getJSON(params.SourceUrl, function(result) {
$.populate_combobox(result, params.SelectId);
});
}
}
var ContentPart_AboutPage_83 = {
Init: function(params) {
$.getJSON(params.SourceUrl, function(result) {
$.populate_combobox(result, params.SelectId);
});
}
}
var ContentPart_AboutPage_84 = {
Init: function(params) {
$.getJSON(params.SourceUrl, function(result) {
$.populate_combobox(result, params.SelectId);
});
}
}
$(document).ready(function() {
ContentPart_AboutPage_82.Init({
SelectId:'ContentPart_AboutPage_82_selectList',
SourceUrl:'/Person'
});
ContentPart_AboutPage_83.Init({
SelectId:'ContentPart_AboutPage_83_selectList',
SourceUrl:'/Person'
});
ContentPart_AboutPage_84.Init({
SelectId:'ContentPart_AboutPage_84_selectList',
SourceUrl:'/Person'
});
});
jQuery.populate_combobox = function(data, selectId) {
$.each(data, function() {
var option = new Option(this.text, this.value);
var dropdownList = $(selectId)[0];
if ($.browser.msie) {
dropdownList.add(option);
} else {
dropdownList.add(option, null);
}
});
}
Update: I have gone even further and added the concept of “SharedVariables” to the ScriptManager where the “SourceUrl” above and even the Json request is fired only once!
Here is my ScriptManager Class:
public class ScriptManager
{
private readonly HtmlHelper _helper;
public IDictionary<string, ClientScript> ClientScripts
{
get
{
if (_helper.ViewContext.HttpContext.Items["ClientScripts"] == null)
_helper.ViewContext.HttpContext.Items["ClientScripts"] = new Dictionary<string, ClientScript>();
return (IDictionary<string, ClientScript>)_helper.ViewContext.HttpContext.Items["ClientScripts"];
}
}
public IDictionary<string, Action> Methods
{
get
{
if (_helper.ViewContext.HttpContext.Items["ScriptMethods"] == null)
_helper.ViewContext.HttpContext.Items["ScriptMethods"] = new Dictionary<string, Action>();
return (IDictionary<string, Action>) _helper.ViewContext.HttpContext.Items["ScriptMethods"];
}
}
public ScriptManager(HtmlHelper helper)
{
_helper = helper;
}
public ClientScript CreateClientScript(string key)
{
if(!ClientScripts.ContainsKey(key))
ClientScripts.Add(key, new ClientScript());
return ClientScripts[key];
}
public ScriptManager AddStaticMethod(string key, Action javascript)
{
if(!Methods.ContainsKey(key))
Methods.Add(key, javascript);
return this;
}
public void Render()
{
TextWriter writer = _helper.ViewContext.HttpContext.Response.Output;
writer.WriteLine("<script type=\"text/javascript\">");
foreach (var clientScript in ClientScripts.Keys)
{
writer.WriteLine("var " + clientScript + " = {");
writer.WriteLine(" Init: function(params) {");
ClientScripts[clientScript].Value();
writer.WriteLine(" }");
writer.WriteLine("}");
}
writer.WriteLine("$(document).ready(function() {");
foreach(var clientScript in ClientScripts.Keys)
{
var client = ClientScripts[clientScript];
writer.WriteLine(" " + clientScript + ".Init({");
foreach (var param in client.Params)
writer.WriteLine(" {0}:'{1}'{2}", param.Key, param.Value, IsLast(client.Params, param.Key) ? string.Empty : ",");
writer.WriteLine(" });");
}
writer.WriteLine("});");
foreach(var method in Methods.Values)
method();
writer.WriteLine("</script>");
}
private static bool IsLast(IDictionary<string, string> parameters, string key)
{
var keys = parameters.Keys.ToList();
return keys.Count > 0 && keys[keys.Count-1] == key;
}
}
Here is the ClientScript class:
public class ClientScript
{
private readonly IDictionary<string, string> _parameters = new Dictionary<string, string>();
private Action _script;
public IDictionary<string, string> Params
{
get { return _parameters; }
}
public ClientScript AddParam(string key, string value)
{
Params.Add(key, value);
return this;
}
public ClientScript Ready(Action script)
{
_script = script;
return this;
}
public Action Value
{
get
{
return _script;
}
}
}
Here is my extension method:
public static ScriptManager ScriptManager(this HtmlHelper helper)
{
return new ScriptManager(helper);
}
And then you only need to add this at the end of your master page:
<% Html.ScriptManager().Render(); %>
I am not a big fan of using checkboxes for a simple yes/no question on a form. I use checkboxes only for selecting multiple items to perform an action on, or when I am using a checkboxlist. The reason for this is because I believe a yes/no question has three possible states, not only two.
If you are asking a user “Do you agree with our terms?”, the general consensus is to assume that not clicking the box is “forgetting” to make a selection and warning me that “You have forgotten to tick our terms and conditions”. In this situation the case may be so, but what if I did not agree? What if the question was “Do you want to opt out of us sending you loads and loads of spam?” and simply not spotting this question.
I believe that if you did not make a selection it is a valid state. Thus a yes/no question, in my opinion, has the following states; “yes”, “no” and “did not choose”.
Once again, I don’t simply rant, I also offer a solution… a very simple extension method which I love using for all my yes/no questions instead of checkboxes. In this case I made the “not selected” state 0.
public static string YesNoDropDownList(this HtmlHelper helper, string id, string selectedValue)
{
var list = new SelectList(new[]
{
new {text = "Select", value = "0"},
new {text = "No", value = "1"},
new {text = "Yes", value = "2"}
}, "value", "text", selectedValue);
return helper.DropDownList(id, list);
}
PS: It also looks a bit neater and more uniform.