Wednesday, November 09, 2011

Refactoring MVC Routes

Some of my team members and myself are participating in the Houston AIR competition this weekend and we are building our project this week. Of course we’ll have to rebuild it from scratch on Saturday, but hopefully it’ll just be a bunch of re-typing and no surprises. Anyhow, one of my duties on the team is to configure MVC and the interfaces we’ll need and all that jazz. So when I went to setup the routes, I was surprised again at how flexible it all is to setup. Typically you can get by with the default route, but not in our case this time. Basically we wanted to have these routes:

/story/give
/story/{id}
/feature/{id}
/prayer/{id}
/{pagename}

By the way, our assigned charity is a church in Houston called the Household of Faith Church - South Acres. At first I had this: (which is TOTALLY RIDCULOUS, but I wanted to see them all)
routes.MapRoute(
"Home",
"",
new { controller = "Main", action = "Home" });

routes.MapRoute(
"GiveStory",
"give/story",
new { controller = "Main", action = "GiveStory" });

routes.MapRoute(
"Feature",
"feature/{id}",
new { controller = "Main", action = "Feature", id=UrlParameter.Optional });

routes.MapRoute(
"Prayer",
"Prayer/{id}",
new { controller = "Main", action = "Prayer", id = UrlParameter.Optional });

routes.MapRoute(
"Story",
"Story/{id}",
new { controller = "Main", action = "Story", id = UrlParameter.Optional });

routes.MapRoute(
"About",
"About",
new { controller = "Main", action = "About"});

routes.MapRoute(
"Directions",
"Directions",
new { controller = "Main", action = "Directions" });

routes.MapRoute(
"SiteMap",
"SiteMap",
new { controller = "Main", action = "SiteMap" });

routes.MapRoute(
"Help",
"Help",
new { controller = "Main", action = "Help" });
I typically setup my routes early on because I usually know what I want the URLs to be and it helps when naming my actions on the controllers. So obvioulsy we only have one controller because it’s not a large site and they all kinda go together, but what’s up with all the actions on the route configuration? Ridiculous right? So let’s get rid of all that mess like this:
routes.MapRoute(
"GiveStory",
"give/story",
new { controller = "Main", action = "GiveStory" });

routes.MapRoute(
"Feature",
"feature/{id}",
new { controller = "Main", action = "Feature", id=UrlParameter.Optional });

routes.MapRoute(
"Prayer",
"Prayer/{id}",
new { controller = "Main", action = "Prayer", id = UrlParameter.Optional });

routes.MapRoute(
"Story",
"Story/{id}",
new { controller = "Main", action = "Story", id = UrlParameter.Optional });

routes.MapRoute(
"Page",
"{viewname}",
new { controller = "Main", action = "GetStatic", viewname = "Home" });
Simple enough right? So I created an action called GetStatic that just accepts the view name for that page. The action looks like this:
public ViewResult GetStatic(string viewname)
{
return View(viewname);
}
Now let’s address the other redundant mess I have here…the {feature/prayer/story}/{id} route. Basically we’re going to do that and add a constraint to the route like this:
routes.MapRoute(
"GiveStory",
"give/story",
new { controller = "Main", action = "GiveStory" });

routes.MapRoute(
"WebSectionRoute",
"{WebType}/{Id}",
new { Controller = "Main", action = "GetWebSection", Id = UrlParameter.Optional }, new {WebType = "Story|Feature|Prayer"});

routes.MapRoute(
"Page",
"{viewname}",
new { controller = "Main", action = "GetStatic", viewname = "Home" });
So what this says is if the WebType contains story/feature/prayer then use this route otherwise keep on going. I can’t express enough the importance of the route order here. It’s an easy thing to miss and I’ve caught the missed order a few times for other developers and myself. Make sur eyou put them in the order they need to be in and not in some random order. Well that’s it. Route configuration refactored and now it doesn’t have any redundant routes.

Thanks for reading!

kick it on DotNetKicks.com

Sunday, October 16, 2011

How to easily turn your L2S objects into interfaces




I know it’s been a while since I posted an actual post, but I think this one will be fun. So let’s get to it…
In one of our recent code reviews, we found several methods that looked almost identical. All of these methods were just for lookup tables that populated a dropdown. So…this is what we did:
We had a repository with something like GetDepartmentList and GetStatusList that looked something like this:
public class SampleRepository : ISampleRepository
    {
        public IList<LookupDepartment> GetDepartmentList()
        {
            using (var dc = new LookupSampleDataContext())
                return dc.LookupDepartments
                                 .Where(x => !x.IsDeleted)
                                 .OrderBy(x=>x.Name)
                                 .ToList();
        }

        public IList<LookupStatus> GetStatusList()
        {
            using (var dc = new LookupSampleDataContext())
                return dc.LookupStatus
                                 .Where(x => !x.IsDeleted)
                                 .OrderBy(x=>x.Status)
                                 .ToList();
        }
    }
Note: we do not return L2S objects typically, but to make this example simple, I’m going to pretend that we do.
The first thing we did was changed the type returned by both methods to return ILookupObject instead of the L2S objects…like this:
public class SampleRepository : ISampleRepository
    {
        public IList<ILookupObject> GetDepartmentList()
        {
            using (var dc = new LookupSampleDataContext())
                return dc.LookupDepartments
                                 .Where(x => !x.IsDeleted)
                                 .OrderBy(x=>x.Name)
                                 .Cast<ILookupObject>()
                                 .ToList();
        }

        public IList<ILookupObject> GetStatusList()
        {
            using (var dc = new LookupSampleDataContext())
                return dc.LookupStatus
                                 .Where(x => !x.IsDeleted)
                                 .OrderBy(x=>x.Status)
                                 .Cast<ILookupObject>()
                                 .ToList();
        }
    }
Obviously this cast isn’t going to work so we need to finagle our dbml. It’s easy to extend the L2S objects without modifying the dbml. I actually prefer never to touch the dbml itself unless absolutely necessary because I like to Ctrl+A, del, drag everything back over whenever I want :)
So to extend the classes, we just need to do this:
public partial class LookupDepartment : ILookupObject
    {
        public string DisplayName { get { return Name; } }
    }

    public partial class LookupStatus : ILookupObject
    {
        public string DisplayName { get { return Status;} }
    }
Done.
Now we need to revisit our SampleRepository and extract out all the redundant code so we have something like this:
private static IList<ILookupObject> GetList<T>(Expression<Func<T, object>> orderby) where T : class, ILookupObject
        {
            using (var dc = new LookupSampleDataContext())
                return dc.GetTable<T>()
                                 .Where(x => !x.IsDeleted)
                                 .OrderBy(orderby)
                                 .Cast<ILookupObject>()
                                 .ToList();
        }
All we’ve done is made a generic GetList method that takes in the orderby. You could easily make this not dependent on the datacontext too, but to keep this post simple, we won’t do that now. As you can see, this looks almost exactly like our original method with the exception of dc.GetTable<T>(), which is built-in to the datacontext class. All we need to do is pass it the type so it knows from which table to pull data. It’s actually a pet peeve of mine for a lookup type dropdown to not be ordered, so I’m forcing the orderby method and not giving another option.
The repository looks like this now:
public class RefactoredSampleRepository : ISampleRepository
    {
        private static IList<ILookupObject> GetList<T>(Expression<Func<T, object>> orderby) where T : class, ILookupObject
        {
            using (var dc = new LookupSampleDataContext())
                return dc.GetTable<T>()
                         .Where(x => !x.IsDeleted)
                         .OrderBy(orderby)
                         .Cast<ILookupObject>()
                         .ToList();
        }

        public IList<ILookupObject> GetDepartmentList()
        {
            return GetList<LookupDepartment>(x=>x.Name);
        }

        public IList<ILookupObject> GetStatusList()
        {
            return GetList<LookupStatus>(x=>x.Status);
        }
    }
Nice and pretty…I don’t like the orderby expression here, but I think it’s the best option for now. There is another way, but it jacks up L2S for inserts and updates. So, if you KNOW your not going to ever update or insert a new record for a lookup, then you could do this:
public partial class LookupStatus : ILookupObject
    {
        [Column(Name="Status")]
        public string DisplayName { get; set; }
    }
What this does is maps the DisplayName to the Status column so you could just do .OrderBy(x=>x.DisplayName) instead of passing in the orderby. The reason you have to map the column is because you’ll get a “SQL has no supported translation…” error. You could also just rename the property Status to DisplayName in the dbml file itself.
Also, it’s not recommended that you use multiple datacontexts according to l2sprof, but to make this simple, I did.
So now we can easily bind this to a dropdown via another helper method like this:
public static class extensions
    {
        public static void BindDropDownTo(this DropDownList ddl, IList<ILookupObject> data)
        {
            ddl.DataSource = data;
            ddl.DataTextField = "DisplayName";
            ddl.DataValueField = "ID";
            ddl.DataBind();
        }
    }
So in the UI, we just do this: (if using webforms)
ddlStatus.BindDropDownTo(rep.GetStatusList());
If you’re using MVC, you could create an htmlhelper to do something similar.
Alright, I think that’s it. Please post if you have better methods or have any questions.
Thanks for reading!
Shout it

kick it on DotNetKicks.com

Sunday, October 09, 2011

Can't believe it...

A whole month went by without me realizing that I didn't blog. It's insane to me how fast a month goes by these days. I'm going to have to step up my blogging again.

At least vacation time is coming up soon, so I'll have more free time.

kick it on DotNetKicks.com

Thursday, August 25, 2011

Blogging just to Blog…I mean Styling Tips!




Lately I have not felt like blogging, but I’m committed to at least once a month. I think the primary reason I haven’t felt th need to blog is because I haven’t really spent much time developing lately. I’ve been spending a lot of my time on user experience and interface design.

I thought I’d share a few styling tricks that are too easy not to do because they add a lot to usability.

  1. Changing the style of a row on hover. This allows the end user to easily see which row they’re looking at instead of having to trace the line.

    table tr:hover td {background: #e7e7e7}
  2. Changing the style of an input on focus. This allows the end-user to easily see where they’re at in a form without much thought.

    input:focus {border: solid 1px #f90}
  3. Changing the style of a hyperlink on hover. Granted this one has been around forever, but it’s a great one.

    a:hover {background: #333; color: #fff}

There are most definitely hundreds more, but these are a few of my favorites to use on a project. Also note that they do NOT work in all browsers.

Thanks for reading!

kick it on DotNetKicks.com

Monday, July 18, 2011

Big Design Conference 2011 Recap




Once again I find myself writing a conference recap without notes. Let me go ahead and apologize to all the speakers just in case I leave out something or misunderstood their presentation.

So…this year’s conference wasn’t as good as last year’s to me. I think there are a few reasons…

1. No free donuts (even though I didn’t care for the orange topping last year…I still liked them…I mean, they’re donuts)

2. No free sodas and snacks between sessions. These are important for any successful conference in my opinion :)

3. It wasn’t all new to me…I knew more of what to expect and what people were going to talk about…not the case last year.

As for the presentations, I’m unsure if they were the same or not as good because I lost perspective as a non-conf-rook. Everything last year was new and fresh and this year, not so much. This year’s conference theme was more about all your customer experiences meshing into one big great customer experience via whatever outlet…last year there seemed to be more of a theme on how to leverage the social web. Anyhow, if you’d like to read that recap you can here.

Day 1:

  • Keynote by Gwen Harmon (@ncrmuseum)
    What I took away…
    • Emotional design makes the whole experience almost life haltering, even if temporarily.
    • The National Civil Rights Museum looks like an amazing place to go visit.
    • Engineers are honest to the point of their own death. I had never heard this joke, it was funny.
      • The Joke: an engineer, a lawyer, and a something else(should’ve taken notes) are all on death row via guillotine. The something else is up, they release the blade and it stops…so he’s let off…the lawyer gets up there the blade drops and it stops again…so he’s let off…the engineer has his turn and looks up and says oh I see your problem.
    • The National Civil Rights Museum is looking to do a lot of upgrades, which will make it even more appealing to visit and learn. You can donate now if you’d like to help.
  • Mobile Design at the Speed of Thought by Michael Griffith @crypticdevice
    Prezi Show
    What I took away…
    • Two ways to communicate – talking & drawing (i.e. not reading requirements)
    • Ideas are disposable…we’ll make more!
    • Downloaded the 106 & Park iPhone app. Played with the voting “game”…highest thus far…30 votes
    • Don’t turn a website into an app! An app is a companion to a website.
    • Buy this book…Understanding Comics The Invisible Art by Scott McCloud
      Also…I bought this book…and 5 others…stupid conference making me spend $
  • Designing a Cohesive Customer Experience by Elizabeth Rosenzweig (@elizrosenzweig) and Scott Gunter
    What I took away…
    • Get to sessions on time because standing for an hour kinda sucks
    • With that said, I know they were talking a lot about Ikea…but I missed most of that part…
    • Don’t shop at Pottery Barn because they will stalk you…at least one of the audience members made it sound this way…I believe it
    • Apparently we are capable of cloaking something for 3 nano seconds according to Elizabeth via MSNBC. Read more here if you wish
    • The jist of this one was to make all of your customer experiences aware of each other…if I create a shopping list on kroger.com and it knows which store I shop at…give me my list on my phone by aisle and sort it based on location so I don’t do 18 laps around the store…seriously they should do this…
  • Free Your Mind: Using UX Checklists to Focus on What's Important by Elisa Miller (@elisakm)
    Download Slide Deck
    What I took away…
    • Make good checklists
    • Buy this book - The Checklist Manifesto by Atul Gawande. (Recommended in more than one presentation I attended)
    • I actually started making quite a few checklists today…the most important: Post-Deployment Review Checklist for applications team
  • Forget your MBA: Managing in a Creative Culture by Marcelo Somers (@marcelosomers)
    Download Slide Deck
    What I took away…
    • He’s a finance geek and a machead
    • Companies are alive and they don’t live very long
    • Focus on a few things and do them better than anyone else…Chipotle was his example
    • The market changes…either change with it or die
  • Designing for Disabilities by Sharron Rush (@sharrush)
    What I would’ve taken away…
    • I’ll admit it…I went for a nap over another session…HOWEVER, I would’ve gone to this one. I did stop by their booth because I plan to participate in their airhouston event in October and was looking for more info. I went to her session last year and it was very eye opening. Seriously, I literally just read the description and am very sorry I missed this one. (Sure wish the description had been on some form of program rather than a print out with titles…hint hint!)
  • Closing Keynote by Russ Unger (who needs no introduction apparently, but gets one anyhow cause he’s special) (@russu)
    What I took away…
    • Russ is very happy. (last year he was angry so whatever he’s doing is working apparently)
    • Russ is writing another book with Todd Zaki Warfel. Guerrilla UX Research Methods released November 15, 2011 according to Amazon.com
    • Russ is still funny.
    • Russ grew a “Texas” mustache just for us
    • Russ thinks we should adopt Ice UX Framework…
      • stop collaborate and listen
      • anything less than the best is a felony
      • quick to the point to the point no faking
      • and there may have been others
    • Russ likes a cat named Maru that gets into small boxes…the cat is funny
    • Russ doesn’t know what he does for a living…or maybe people that he is close to don’t know…I don’t remember if he ever actually said
    • Russ is excited to see what we do…I’m curious and a little excited too :)

Day 2:

  • Opening Keynote by Josh Clark (@globalmoxie)
    • I slept in due to lack of description. I need to know what I’m waking up for :)
  • Accelerated Style Sheets: Less Typing, More Style by Wynn Netherland (@pengwynn) and Nathan Smith (@nathansmith)
    • Intended to go to this, but was caught up with packing and checking out of my room
  • Designing for Change and Innovation by Sara Summers (@sarasummers)
    What I took away…
    • She wrote a book. I was going to buy it, but then I saw it was using Expression Blend…
    • Use your conference waiting time to ask people about ideas you have
    • Make a feedback area in the blank walls of the surrounding cubes
    • Walk the dog to think or actually just make time to think – I actually did that this morning on my way into work. I have a 45 minute commute and left the radio off so I could think…it was nice to talk to me.
  • Innovation: Ideas are Just the Beginning by Adam Polansky (@adamtheia)
    Download Slide Deck
    What I took away…
    • An extra 30 minutes to look around…he was a bit quick
    • Innovation = New Combinations (i.e. guy driving thinks “I like listening to radio”…”HEY NOW! I should add a radio in my car!”)
    • Create a list of small bug fixes that can be fixed on your own time…present them…they’ll probably get added to the project list
  • Slay the Legacy Code Beast by Caleb Jenkins (@calebjenkins)
    Download Slide Deck
    What I took away…
    • Caleb is here to help.
    • Caleb looks and talks a LOT like Dane Cook (I think Caleb might be funnier)
    • Caleb references presentations from a year ago (I should’ve gone I guess)
    • Caleb likes to ROOOOAAARRR (or just make monster noises…not sure how to spell what he was doing) and he enjoys audience participation
    • Caleb once worked on a solution with over 68 projects…I think I woulda quit
    • Here’s the jist:
      • Know Your Foe (Legacy Code)
        • Don’t go changing stuff willy nilly
        • Use dependency graphs
      • Create a Safety Net
        • Use Source Control
        • Create Tests
      • Divide and Conquer (pick your battles)
        • The slideshare.net doesn’t do his presentation justice…I mean there was a really nifty split of the screen for this 3rd strategy announcement during his live presentation…it was truly amazing.
    • Caleb will give you credit if he takes one of your pics from flickr…good to know
    • I really enjoyed Caleb’s talk even though I don’t have every day battles with legacy monsters
  • Designing for Awareness by Brian Sullivan (@BrianKSullivan) and Taylor Cowan (@tcowan)
    Download Slide Deck
    What I took away…
    • Attention = Focus
    • Distraction = Disorder
    • Information’s price is attention…don’t be poor on attention
    • Two types of attention…active and passive
    • Do you multi-task? No you don’t.
    • Checklists mentioned again
    • Interruption vs Notification – Very important in design
      • Interruption = Active Awareness (i.e. alert(‘Hey there!’);)
        • Only in emergency or dire situation
      • Notifications = Passive Awareness (i.e. $(‘#thissmallredsquarenotification’).show();)
    • We process 400 billion bits of data per second passively
      • A lot less actively…2,000
  • Progressive Prototyping with HTML5, CSS3, and jQuery by Todd Zaki Warfel (@zakiwarfel)
    Download Slide Deck
    What I took away…
    • Todd likes to cook
    • Todd makes grayscale look sexy
    • Todd does not wireframe…I still do…I prototype too…mostly I wireframe to help me visualize the markup and gen the graphics
      • Todd does this via sketches…I’m not that good
      • I wonder what kind of markers he uses because they look like some awesome colors on his slideshow
    • Todd prefers to fail in a low cost environment…imagine that…
    • Todd lays out the data first…then he styles
    • Todd bragged a lot about not having a single image on his grayscale and it still looking “sexy”
    • Todd then went on about the awesomeness of HTML5
      • Specifically the progress and meter tags, which are pretty freakin sweet
    • Todd gave a heads up about updating your reset.css…I’m glad he did because I have not
    • Todd provides great coverage of CSS3 Selectors…I’ll let you look at his deck…same with his jQuery coverage
    • Todd wrote a book and is working on the one mentioned earlier with Russ Unger…I bought the Prototyping book and am reading it now
  • Mobile & UX by Jared Spool
    Download Slide Deck…OH wait I haven’t received it via email yet…hopefully I emailed them correctly and will get it soon and hopefully I can get permission to share…if not…you shoulda been there :)
    What I took away…
    • Jared is flippin smart.
    • Jared is great at teaching and entertaining…not once did I think, “hey! I’m learning a lot” (I did though)
      • Sturgeon’s Law – 90% of everything is crap. Are you in the 10%?
      • Technology » Features » Experience
        • Jared realizes on an example here that his audience is a young one
      • The Kano Model
        • User Satisfaction by Company Investment
        • Must meet basic expectations (BEs) before excitement generators work
          • When BEs don’t work, you’re just pissing people off
          • With time, delighters become BEs
      • There were 3 questions to see how well formed your team is…but I don’t remember them. Hopefully I get that slide deck soon :)
        What I remember…
        • the vision of the team and their experience with your design
        • how long you spend watching your’s or your competitor’s design
        • have you rewarded failure
    • We are in an immature era, we are learning and the next few years are going to be very exciting and a bit scary if you like your privacy. Seriously…scary.

Books Bought Due to Conference:

Other Stuff

Suggestions for next year

  1. Printed programs with full descriptions of presentations and speakers
  2. Donuts
  3. Snacks
  4. Sodas
  5. Casino night that I know about at least a day or two before the night actually occurs…I would’ve loved to shoot some craps
  6. Donuts
  7. Maybe some bacon??

That about does it. I enjoyed the conference and I’m excited about the user experiences I’ll be creating in the next year. Look forward to next year’s event!

Thanks for reading!

kick it on DotNetKicks.com

Thursday, June 16, 2011

jQuery Plugin: FunkyFocus v1.1


I used by little plugin in a production environment today and found one bug and a couple needed features.

The bug was with this code:

options.selectorOverride.replace('{id}', this.id);
 
Apparently it only replaces the first {id} found in the string. So I updated that line to this:
 
var selector = options.selectorOverride.replace(/{id}/gi, this.id);

One of the features I needed was a way to ignore certain elements. So I created a new option called notSelector and the default is ‘.ignore’. So basically all the selectors are $(selector).not(options.notSelector).whatever.

I also added a check for when you set the focus of an input via the code-behind in ASP.NET.

You can download the new version here.

Check out the demo here.

Thanks for reading!

kick it on DotNetKicks.com

Thursday, May 26, 2011

New jQuery Plugin: FunkyFocus


So this jQuery plugin was built to help my end-users know immediately what section of a form they’re working on. Also, don’t blame me for the name. It was given to my new plugin by my wife because I couldn’t think of anything “catchy”. Basically what this thing does is changes the background of sections of forms or individual inputs. It’ll be better to post screenshots to get the idea…so…here you go…

image

So as soon as you tab to the password textbox, you get this:

image

Here’s how you use it:

  1. Add a reference to jQuery
  2. Add a reference to the jquery.funkyfocus.js
  3. Add this line afterward: $('form').funkyFocus();

If you want to customize how you use it, you have some options. Here they are:

  • class – default is selected
  • sectionOnly – default is true
  • selectorOverride – default is form#{id} input,form#{id} select,form#{id} textarea

If you don’t want to use selected as your class, you’d use this code:

    $('form').funkyFocus({class: 'yourclassname'});

If you don’t want to change the background, but want to change the individual input, do this:

    $('form').funkyFocus({sectionOnly: false});

It would look something like this (assuming you changed the selected style to have a green background):

image

Here’s the actual plugin code:

(function($) {
    $.fn.funkyFocus = function(options) {
  var defaults = {
    class: 'selected',
    sectionOnly: true,
    selectorOverride: 'form#{id} input,form#{id} select,form#{id} textarea'
  };
  var options = $.extend(defaults, options);
  return this.each(function() {
    var selector = options.selectorOverride.replace('{id}', this.id);
    $(selector).focus(function() {
      if (options.sectionOnly)
        $(this).closest("div").addClass(options.class);
      else
        $(this).addClass(options.class);
    });
    $(selector).blur(function() {
      if (options.sectionOnly)
        $(this).closest("div").removeClass(options.class);
      else
        $(this).removeClass(options.class);
    });
        });
    };
})(jQuery);

If you’d like to know how to create a jQuery plugin, check out this post.

Download the plugin here.

Check out the demo here.

As always, please let me know if there’s a way to improve it. Thanks for reading!

Shout it

kick it on DotNetKicks.com

Sunday, May 22, 2011

Contact Form Revisited with ASP.NET MVC 3, jQuery Validator, & the jQuery Form Plugin




I recently added a partial contact view to my MVC3 project and thought I’d share since I did basically this same post over a year ago with the original MVC. I stopped using the Castle Validation because I’ve found that the MVC3 stuff is working for me now. I also am not using fluentHtml anymore because MVC3 uses that style now.

Okay…let’s get started.

We’ll start with the view model class like the last post. By the way, if you’re not familiar with the way I setup my MVC projects, see this post. (If you read my first Contact Form post, you’re probably experiencing déjà vu).

public class ContactView
{
[Required]
public string Name { get; set; }
[Required, ValidateEmail(ErrorMessage = "Valid email is required.")]
public string Email { get; set; }
[Required]
public string Subject { get; set; }
[Required]
public string Message { get; set; }
}

The Required attribute is exactly what it seems like and it’s part of the System.ComponentModel.DataAnnotations. The default error message is “The [propertyname] is required.”. If you want to reset it, you can do this: [Required(ErrorMessage = “Whatever I want it to be”)].

The ValidateEmail is a custom validation attribute. I didn’t like the looks of having a RegularExpressionAttribute defined there and since email is such a common thing to validate, I made this one:

public class ValidateEmailAttribute : RegularExpressionAttribute
{
public ValidateEmailAttribute(): base(@"[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?")
{
}
}

It just inherits from the RegularExpressionAttribute and passes in the regex to validate an email address. Apparently there is much discussion on how to validate an email address, which I believe is why Microsoft didn’t provide this to us.

So let’s look at the View/XHTML Markup.

@model Sample.Core.UI.Model.ContactView
@using Sample.Core.UI.Helpers
@using (Html.BeginForm("Contact", "Home", FormMethod.Post, new { id = "contactform" }))
{
<fieldset>
<legend>Contact Us</legend>
    <p>
@Html.LabelFor(f => f.Name, "Your Name")<br />
@Html.TextBoxFor(f => f.Name, new { style = "width: 200px", @class="required", accesskey="n" })
</p>
<p>
@Html.LabelFor(f => f.Email, "Your Email")<br />
@Html.TextBoxFor(f => f.Email, new { style = "width: 200px", accesskey = "e" })
</p>
<p>
@Html.LabelFor(f => f.Subject, "Subject")<br />
@Html.TextBoxFor(f => f.Subject, new { style = "width: 200px", accesskey = "p" })
</p>
    <p>
@Html.LabelFor(f => f.Message, "Message")<br />
@Html.TextAreaFor(f => f.Message, new { style = "width: 350px", rows = "4", accesskey = "c" })
</p>
@Html.AntiForgeryToken()
<input type="submit" id="bContact" name="bContact" value="Send Message" accesskey="s" /> 
@Html.DivSuccessMessage("Message sent successfully", "contactsuccess") 
@Html.ValidationSummary("", new { id = "contacterror" })
<noscript><br /><br /><div class="tip">Our contact form may look and act funny because you have JavaScript disabled. For a better experience on thissample.com, please enable JavaScript.</div></noscript>
</fieldset>
}

You can see at the top of the page I specify my view model. I ‘m also referencing a helper namespace for my DivSuccessMessage extension. Basically all it does is checks the ModelState for errors and for ViewData[“success”] not being null and displays the message specified. After that it’s basically a plain ole HTML form.

Okay, now we have our form built with our view model. Below the end of the XHTML above, I have the following jQuery code.

<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.5.2/jquery.min.js"></script>
<script type="text/javascript" src="http://ajax.aspnetcdn.com/ajax/jquery.validate/1.8/jquery.validate.min.js"></script>
<script type="text/javascript" src="http://www.malsup.com/jquery/form/jquery.form.js"></script><script language="javascript" type="text/javascript">
$(document).ready(function () {
var formoptions = { beforeSubmit: function (formData, jqForm, options) {
$("#bContact").attr('value', 'sending...');
$("#bContact").attr('disabled', 'disabled');
}, success: function (data) {
if (data.status == "Success") {
$("#contacterror").hide();
showMessage("#contactsuccess", data.message);
$('.valid').removeClass('valid');
validator.resetForm();
}
else {
$("#contactsuccess").hide();
showMessage("#contacterror", data.message);
}
$("#bContact").attr('value', 'Send Message');
$("#bContact").removeAttr('disabled');
}, dataType: "json"
};jQuery.validator.messages.required = "";
var validator = $("#contactform").validate({
submitHandler: function (form) {
$(form).ajaxSubmit(formoptions);
},
invalidHandler: function (e, validator) {
var errors = validator.numberOfInvalids();
if (errors) {
var message = errors == 1 ? 'You missed 1 field. It has been highlighted.' : 'You missed ' + errors + ' fields.  They have been highlighted.';
showMessage("#contacterror", message);
$("#contactsuccess").hide();
} else {
$("#contacterror").hide();
}
},
messages: { Email: { email: ""} },
rules: {
Subject: "required",
Message: "required",
Email: { required: true, email: true }
},
errorClass: "invalid",
validClass: "valid",
errorContainer: "#contacterror"
});function showMessage(id, message) {
$(id).html(message);
$(id).show();
}
});
</script>

Let’s break this down. First section the formOptions:

var formoptions = { beforeSubmit: function (formData, jqForm, options) {
$("#bContact").attr('value', 'sending...');
$("#bContact").attr('disabled', 'disabled');
}, success: function (data) {
if (data.status == "Success") {
$("#contacterror").hide();
showMessage("#contactsuccess", data.message);
$('.valid').removeClass('valid');
validator.resetForm();
}
else {
$("#contactsuccess").hide();
showMessage("#contacterror", data.message);
}
$("#bContact").attr('value', 'Send Message');
$("#bContact").removeAttr('disabled');
}, dataType: "json"
};

This code is used to define all my options for the jQuery.Form plugin. What it says is this:

  • beforeSubmit – Change the button to say “sending…” and disable it
  • on success – if the status = “Success” then hide the contacterror div, show the success message, manually remove the valid class from my inputs, and reset the form. Otherwise, hide the success message and show the error message with the message received. Regardless, re-enable my button and make it say “Send Message”. (Note: I shouldn’t have to manually remove the valid class, but the resetForm wouldn’t do it for me like it’s supposed to do.)
  • dataType – json received from my action

The validation section:

jQuery.validator.messages.required = "";
var validator = $("#contactform").validate({
submitHandler: function (form) {
$(form).ajaxSubmit(formoptions);
},
invalidHandler: function (e, validator) {
var errors = validator.numberOfInvalids();
if (errors) {
var message = errors == 1 ? 'You missed 1 field. It has been highlighted.' : 'You missed ' + errors + ' fields.  They have been highlighted.';
showMessage("#contacterror", message);
$("#contactsuccess").hide();
} else {
$("#contacterror").hide();
}
},
messages: { Email: { email: ""} },
rules: {
Subject: "required",
Message: "required",
Email: { required: true, email: true }
},
errorClass: "invalid",
validClass: "valid",
errorContainer: "#contacterror"
});

This code defines my validation for the contact form. It says this:

  • Set all messages for required fields to empty by default
  • submitHandler – on submit do this, which it calls the ajaxSubmit contained in the jQuery.Form plugin with our options specified above.
  • invalidHandler – if the form isn’t valid, get the number of errors and show the error message. Otherwise, hide the error message.
  • messages – Defines what the message should be for email, which is empty. I would’ve had to specify the required messages too had I not set them to empty first. Also note that the Email: has to match the ID of one of  your inputs.
    • Example: <input type=”text” id=”whateverid”/> so the messages would looks like this:

      messages: {whateverid: {required: “some message”}}

  • rules – Defines the rules for each input. Phone, Comments are required and Email has required and email. Notice name is required, but not specified here. It’s because I added the required class to the Name input in the XHTML instead of specifying down here so you could see you have options.
  • errorClass – Specifies my style class for when the input is invalid.
  • validClass – Specifies my style class for when the input is valid.
  • errorContainer – Specifies the div I want to show my error messages in.

Final section:

function showMessage(id, message) {
$(id).html(message);
$(id).show();
}

This just finds the container sets the html and shows it.

Okay, so finally here’s what the controller looks like that we mentioned in the @Html.BeginForm() section above.

[HttpPost, ValidateAntiForgeryToken, ValidateInput(true)]
public ActionResult Contact(ContactView view)
{
if (!ModelState.IsValid)
{
if (Request.IsAjaxRequest())
return Json(new { status = "error", message = "All fields are required." });

return View(view);
}    try
{
var notificationService = DI.EmailNotificationService(new EmailNotification(view));
notificationService.Notify();
}
catch (NotificationException)
{
ModelState.AddModelError("notifyerror", "Could not connect to mail server.");
}    if (Request.IsAjaxRequest())
return ModelState.IsValid ? Json(new {status = "Success", message = "Message sent successfully."}) : Json(new {status = "error", message = "Could not connect to mail server."});    return ModelState.IsValid ? Success(view) : View(view);
}

So this action accepts HttpPost, must have a valid Anti-Forgery Token, and it validates the input. First thing it does is verifies the modelstate is valid. The reason for this is that some people run their browser with JavaScript disabled. So we have to account for that in our code and make sure that we are validating on the client-side and on the server-side. So if the ModelState is invalid, we have to check to see if it’s an AJAX request. if it is, we return a Json result with the status of error and a message stating all fields are required. If it’s not an AJAX request, we simply return the view.

If all is valid, we continue and go ahead and send the notification. If the notification bombs, we add an error to the modelstate and then recheck and act accordingly. If you want to know what the notification service looks like, please refer to the first post because it’s all the exact same.

So, this method of coding will work when JavaScript is enabled and disabled and all the data will be validated regardless as well.

Here’s what the screen looks like after just hitting Send Message:

image

Here’s what it looks like after all the fields are valid right before I hit Send Message:

image

After message sent:

image

Please let me know if you have any questions.

Download Demo

Thanks for reading!

Shout it

kick it on DotNetKicks.com

Monday, May 09, 2011

Seriously WebForms like ASP.NET MVC




I used my little WebFormContrib library again today. Some days I love revisiting old code because you realize how ignorant of some practices you were in the past. Hopefully none of you download it and say…geez this guy is Mr. Ignoramus. If you do, keep it to yourself. Kidding, please comment and inform the ignorant (me).

Anyhow, it’s been about 6 months since I’ve had to use WebForms, but today I had to and it wasn’t bad. I was able to tie my view into my pages and controls and used AutoMapper to map back to the domain from it. To me, WebFormContrib makes WebForms kinda fun again…cause it makes it seem new. I really do think it’s a great stepping stone to using MVC just because you kinda get used to the syntax. I also think it’s a decent library because I didn’t have to go relearn how it worked, I just referenced the library and then started working on my little WebForm app. I set it up just like I do my MVC apps. I also had to add a couple things and it was easily extendable, which I’m sure you all know is a good thing.

Anyhow, if you have no idea what I’m talking about, please read my previous posts on WebFormContrib.

Also, how could I post without a code sample? In “The Original” post, I mentioned the first thing I wanted to refactor was the validation section. Well, I did. Here’s the new and improved ModelIsValid() method:

        internal bool ModelIsValid(TModel view)
{
ErrorMessages =
new List<string>();
foreach (var property in typeof(TModel).GetProperties())
{
var value = property.GetValue(view, null);

var attributes = property.GetCustomAttributes(typeof(IValidationAttribute), false);
foreach (IValidationAttribute valatt in attributes)
if (!valatt.IsValid(value))
ErrorMessages.Add(valatt.Message);
}

return ErrorMessages.Count == 0;
}

Previously, it looked like this:

        internal bool ModelIsValid(TModel view)
{
ErrorMessages =
new List<string>();
foreach (var property in typeof(TModel).GetProperties())
{
var attributes = property.GetCustomAttributes(typeof(RequiredAttribute), false);
if (attributes.Length > 0)
{
var value = property.GetValue(view, null);
if (value.ToSafeString() == string.Empty)
ErrorMessages.Add(((
RequiredAttribute)attributes[0]).Message);
}
}

if (ErrorMessages.Count == 0)
return true;

return false;
}

Obviously…I was a moron. I still don’t think it’s perfect, but it is a serious improvement. It’s funny how you don’t see how to refactor something until you need to extend it. As soon as I created the length validator and added the code here I thought…wow this is dumb and then OH! do it this way. Anyhow, I thought I’d share the slight improvement.

You can download the source and samples here…or just download the DLL here.

Thanks for reading!

Shout it

kick it on DotNetKicks.com

Related Posts Plugin for WordPress, Blogger...