Using ErrorHandlers and singletons in WCF

by MikeHogg 19. September 2012 20:16

I had a COM wrapper that I wrote for a third party interface.  I knew I was going to use it not only in a desktop application, but also in our web application and our vendor’s web application, and also possibly further down the road in a mobile application, so I wrote a WCF web service that implemented my wrapper’s functionality rather than drop that project in multiple solutions. 

Writing the web service is easy enough.  For years in Visual Studio you can just create a WCF Project and figure out some options and add some classes.  For options you choose between NetTcp or Http binding. I have used NetTcp in intranet scenarios, but in this case I needed HttpBinding, so I could choose WS or Basic.  As WS doesn’t easily work with non .Net clients I chose Basic for this case, although I later found out this didn’t help me with my mobile client as I expected. 

The other option you usually always have to figure out is your authentication.  In this case I used basic forms authentication over https, so my service model was really simple.  I have set up services that implemented X509 client certificates, but it can be complicated and this project didn’t need that layer of security. 

 <system.serviceModel>
    <extensions>
      <behaviorExtensions>
        <add name="ErrorLogging" type="My.lib.WCF.ErrorHandlerBehavior, Mylib" />
      </behaviorExtensions>
    </extensions>
    
    <services>
      <service name="SomeSN.SomeService">
        <endpoint  binding="basicHttpBinding"
          bindingConfiguration="SSWebBinding" contract="SomeSN.ISomeService"/>
      </service>
    </services>
    <bindings>
      <basicHttpBinding>
        <binding name="SSWebBinding" sendTimeout="00:02:00">
          <security mode="TransportWithMessageCredential">
            <message clientCredentialType="UserName"/>
          </security>
        </binding>
      </basicHttpBinding>
    </bindings>
    <behaviors>
      <serviceBehaviors>
        <behavior>
          <ErrorLogging/>
          <serviceCredentials>
            <userNameAuthentication userNamePasswordValidationMode="Custom"
              customUserNamePasswordValidatorType="Some.CustomPasswordValidator, Some" />
          </serviceCredentials>
          <!-- To avoid disclosing metadata information, set the value below to false and remove the metadata endpoint above before deployment -->
          <serviceMetadata httpGetEnabled="false" httpsGetEnabled="true"/>
          <!-- To receive exception details in faults for debugging purposes, set the value below to true.  Set to false before deployment to avoid disclosing exception information -->
          <serviceDebug includeExceptionDetailInFaults="false"/>
        </behavior>
      </serviceBehaviors>
    </behaviors>
    <serviceHostingEnvironment multipleSiteBindingsEnabled="true" />
  </system.serviceModel>
If I remember right I needed to make MultipleSiteBindingsEnabled in this case for our IIS servers having other webs running. 

 

You will see at the bottom I added my ErrorHandling behavior, and my custom password validator, which was basically my password interface, in this case pointing just to the config files.

    class CustomPasswordValidator : System.IdentityModel.Selectors.UserNamePasswordValidator
    {
        public override void Validate(string userName, string password)
        {
            string[] serviceusernames = My.lib.ConfigHelper.GetAppSetting("SOME_USER", (string)null).Split(new char[]{ ';'});  //  quick solution to user mgmt assuming we only ever have two users (our website and our vendor's website) and they can share a password
            string servicepassword = My.lib.ConfigHelper.GetAppSetting("SOMe_PASS", (string)null);
            if ( !serviceusernames.Contains(userName) || String.IsNullOrEmpty(password) || !password.Equals(servicepassword) )
                throw new System.ServiceModel.FaultException("Authentication failed."); // this will just get wrapped by a messagesecurityexception an kick em out
        }
    }

 

The ErrorHandler was an interesting case, as it came up in my testing.  My COM component would fail occasionally, and it was expected to pass meaningful error messages.  I built the wrapper to fail also and pass these messages as exceptions, but when the Web Service would throw exceptions, it would just replace my meaningful messages with a general SOAP exception, and then freeze up the server to more incoming requests, which was very bad.  So I had to figure out a way to preserve those messages (easy if I turn them into messages instead of exceptions but my stack was built to use these exceptions already and this would mean lots of extra code for what I really intended to be exceptional) and not break the service, which wasn’t easy (don’t throw SOAPExceptions).  Here I just implement the Interfaces and the important part is in returning true in HandleError and in creating my MessageFault from my wrapper exceptions in ProvideFault…

    public class ErrorHandler : IErrorHandler, IServiceBehavior
    {
        public bool HandleError(Exception error)
        {
            // Logger.LogError(error);  
            return true;
        }
        public void ProvideFault(Exception error, MessageVersion version, ref Message fault)
        {
            FaultException faultException = new FaultException(error.Message);
            MessageFault messageFault = faultException.CreateMessageFault();
            fault = Message.CreateMessage(version, messageFault, faultException.Action);
        }
        public void AddBindingParameters(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase, System.Collections.ObjectModel.Collection<System.ServiceModel.Description.ServiceEndpoint> endpoints, System.ServiceModel.Channels.BindingParameterCollection bindingParameters)
        { 
        }
        public void ApplyDispatchBehavior(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
        {
            IErrorHandler errorHandler = new ErrorHandler();
            foreach (ChannelDispatcherBase channelDispatcherBase in serviceHostBase.ChannelDispatchers)
            {
                ChannelDispatcher channelDispatcher = channelDispatcherBase as ChannelDispatcher;
                channelDispatcher.ErrorHandlers.Add(errorHandler);
            }
        }
        public void Validate(System.ServiceModel.Description.ServiceDescription serviceDescription, System.ServiceModel.ServiceHostBase serviceHostBase)
        { 
        }
    }
    class ErrorHandlerBehavior : System.ServiceModel.Configuration.BehaviorExtensionElement
    {
        public override Type BehaviorType
        {
            get
            {
                return typeof(ErrorHandler);
            }
        }
        protected override object CreateBehavior()
        {
            return new ErrorHandler();
        }
    }

With that I could now leave my exceptions as exceptions, and my stack would bubble up as expected, even across WCF to the client, with my meaningful messages, without freezing my channels.

The rest of my Service was simple also, a handful of OperationContracts, not even passing complex classes, except one IEnumerable<MyCustomType>.  And my Server class was also too simple to even post, since it was just a call to my COM wrapper for each web service method.  But the instantiation and initialization of the COM component and the wrapper was time intensive.  It took a few to ten seconds to create it and get it ready for use. So I wanted a way to create the wrapper once, and then use it over and over for each request, so I used a singleton pattern…

    // This class is so we can spin up one instance to handle a number of requests at once.  
    // In practice, it appears IIS will keep it in memory for a short length of time, like for several calls on one page, 
    // but it seems to dispose of it after a few minutes.... would like to investigate further...
    public sealed class MySingletonWrapper
    {
        MyWrapperClient.AdaptorClient _client; 
        static MySingletonWrapper _client = null;
        static readonly object padlock = new object();
        public static MySingletonWrapper Client
        {
            get
            {
                lock (padlock)
                {
                    if (_client == null)
                    {
                        _client = new MySingletonWrapper();
                    }
                    return _client;
                }
            }
        }
        MySingletonWrapper()
        {
            _client = GetMyWrapperClient(); 
        }
        /// <summary>
        /// initializes and prepares for transactions using properties from config file
        /// </summary>
        /// <returns></returns>
        MyWrapperClient.AdaptorClient GetMyWrapperClient(string somewireid = null, string anotherid = null, string anybodyskeyid = null)
        {
            string primary = My.lib.ConfigHelper.GetAppSetting("SS_PrimaryUrl", "https://somedomain.com/urlforsomething");
			string secondary = My.lib.ConfigHelper.GetAppSetting("SS_SecondaryUrl", "https://somedomain.com/urlforsomething");
            string whatid = My.lib.ConfigHelper.GetAppSetting("SS_MerchantId", "2323232323232323");
            if (String.IsNullOrEmpty(anotherid)) terminalid = My.lib.ConfigHelper.GetAppSetting("SS_TerminalId", "00000000001");
            if (String.IsNullOrEmpty(somewireid)) datawireid = My.lib.ConfigHelper.GetAppSetting("SS_SomeWireId", "12121212121212121212");
            if (String.IsNullOrEmpty(anybodyskeyid)) merchantkeyid = My.lib.ConfigHelper.GetAppSetting("SS_AnybodyskeyId", "2222222");
            string arefnum = My.lib.ConfigHelper.GetAppSetting("SS_ARefNum", "55555555"); 
            string whatkingkey = My.lib.ConfigHelper.GetAppSetting("SS_WhatKingKey", "88883333888833333888883333338888888333333");
            return new MyWrapperClient.AdaptorClient(primary, secondary, whatidid, anotherid, arefnum, somewireid, anybodyskeyid, WhatKingkey);
        }
        public IEnumerable<Transaction> GetTransHistory(string cardnumber, string pin)
        {
            List<Transaction> result = new List<Transaction>();
            foreach (MyWrapperClient.FSSransaction t in _client.GetTransactionHistory(cardnumber, pin)) result.Add(new Transaction(t)); }
            return result;
        }

 

This sped up my requests to sub-second times, although note where I found in IIS 7.5 there was some automatic memory management going on… something to look into for next time.

More js plugin fun, this time with with google maps

by MikeHogg 6. September 2012 09:40

I don't believe I've written this up anywhere and it might be useful to refer to in the future...  I did a rather extensive page in vb aspnet webforms that included mapping several markers on a google map.  Several lessons learned throughout.  Here is the mature code...

 

I was told to use a ListView control, and they wanted paging, so I hooked up the DataPager control to that, but they wanted the map to show everything in the list, not just the displayed page, so I just included a separate hidden listview, with my required data elements (all of them), no paging.  The data is a list of cars, each with a zip code as the sole location data.  I was to use this limited info to create markers for each car on the map.   Oh and the data was in a foreign language  :)  Everything else about the List should be standard here...

 

Added to the bottom of the html in this case...

<script language="javascript" type="text/javascript" src="https://maps.googleapis.com/maps/api/js?key=AIsomething&sensor=false"></script>
 <!-- deploy this script AFTER the maps api-->
 <script language="javascript" src="../_scripts/google-maps-3-vs-1-0.js" type="text/javascript"></script>
 <script language="javascript" type="text/javascript">
  var map; 
  var cars;
  var infowindow;
  var rollovers = [];
  var latlngprocessed = [];
  
  function init() {
   var mapOptions = {
    center: new google.maps.LatLng(51.55, 4.28), // default center
    zoom: 7,
    mapTypeId: google.maps.MapTypeId.ROADMAP
   };
               
   map = new google.maps.Map($("#CVMap")[0], mapOptions); 
 
   cars = getAllCars();
 
   $(cars).each(function () {
       if (!latLngAlreadyProcessed(this)) {                
           //  it's likely there exists more than one car at a particular latlng           
           var allmatches = getAllMatches(this);
 
           var image = new google.maps.MarkerImage('../_css/img/org_dwn_arrow.png',
                      new google.maps.Size(17, 19),
                      new google.maps.Point(0, 0),
                      new google.maps.Point(0, 19));
           var shadow = new google.maps.MarkerImage('../_css/img/org_dwn_arrow.png',
                      new google.maps.Size(40, 19),
                      new google.maps.Point(0, 0),
                      new google.maps.Point(0, 19));
           var shape = { coord: [1, 1, 1, 17, 19, 17, 19, 1],  type: 'poly' };
 
           var marker = new google.maps.Marker({
               map: map,
               position: getGoogleLatLng(this),
               shadow: shadow,
               icon: image,
               shape: shape,
               title: allmatches.length > 1 ? (allmatches.length) + ' cars' : this.titlelink.text
           });
 
           centerMap(allmatches);
           createInfoWindow();
 
           var $rollovercontent = $('<div class="carrollover" id="CarRollover"><h1></h1><ul></ul></div>');
           $.each(allmatches, function (idx, val) {
               var $item = $('<li></li>').html($(val.titlelink).clone())
        , $title = $('h1', $rollovercontent);
               if ($title.text() == '') {
                   $title.text('Cars in ' + val.location);
               }
               $('ul', $rollovercontent).append($item);
           });
 
           // closure and a separate array of rollovercontent needed here, because there is only one infowindow per map
           var i = rollovers.length; // get before push so we have index for closure below
           rollovers.push(getStringFromJqueryObject($rollovercontent));
 
           google.maps.event.addListener(marker, 'mouseover', (function (mark, idx) {
               return function () {
                   infowindow.setContent(rollovers[idx]);
                   infowindow.open(map, mark);
                   $('#CarRollover').parent().css('overflow-x', 'hidden');
                   setTimeout(function () {
                       $fix = $('#CarRollover').parent().parent();
                       $fix.css({'top' : '28px'});
                   }, 200);
               };
           })(marker, i));
       }
   });
  }

 

The closure is nothing more than creating distinct function instances on the fly for each rollover, since google maps only have one InfoWindow, we need to replace the content of it with that particular marker's info.  Also we merge info so a marker with several cars sharing the same zip code would show as a list of links.

The rest is just some standard helper jQuery-fu functions.

 

function centerMap(allmatches) { // to first in resultslist
      if ($.grep(allmatches, function (v) { return v.index == 0; }).length > 0) {
          map.setCenter(getGoogleLatLng(allmatches[0]));
      }
  }
 
  function createInfoWindow() {  // if not already created (google says only one per map)
      if (!infowindow) {
          infowindow = new google.maps.InfoWindow({
              maxWidth: 400
          });
      }
  }
  function getAllCars() {  
      var titlelinks = $("#hiddenformap .nameformap");
      var descriptions = $("#hiddenformap .shortdescriptionformap").map(function () { return $(this).text(); }).get();
      var locations = $(".hiddenlocationformap").map(function () { return $(this).text(); }).get();
      var latlngs = $(".hiddenlatlngformap").map(function () { return $(this).text(); }).get(); 
      var result = [];
      $.each(latlngs, function (idx, val) {
          if (val != '') {
              result.push({  
                        'index': idx,
                  'location': locations[idx],
                  'latlng': val,
                  'titlelink': titlelinks[idx],
                  'description': descriptions[idx]   });
          }
      });
   return result;
  }
 
  function getAllMatches(car) {
      return $.merge($.grep(cars, function (c) { return car.latlng == c.latlng && c != car; }), [car]);
  }
 
  function getGoogleLatLng(car) {
      return new google.maps.LatLng(car.latlng.split(",")[0], car.latlng.split(",")[1]);
  }
 
  function getStringFromJqueryObject(obj) {
      return $('<div>').append($(obj).clone()).html();
  }
 
        function latLngAlreadyProcessed(car) {
            var result = $.grep(latlngprocessed, function (ll) {
                return ll == car.latlng;
            }).length > 0;
            if (result == false) { latlngprocessed.push(car.latlng); }
            return result;
        }
One other thing to mention was that I was querying google for latitude and longitude and storing it server side, so as not to pound their geolocation service, as they requested in terms of service and by applying a few different limits.  So rather than sending zipcode (if you read the code above notice it should not send zip) it sends latlng which it already has.  Here is the server side code for google's geo service (note my XML library calls, which I now replace with 3.5 xml literals [yay] since I just found out about them)...

 

 

Private Function GetGoogleLatLng(ByVal c As Car, ByVal trynumber As Int16) As String
        Dim url As String = String.Format("http://maps.googleapis.com/maps/api/geocode/xml?sensor=false&address={0}", HttpUtility.HtmlEncode(j.FunctionLocationPostalCode))
        Dim x As XDocument = XDocument.Load(url)
        Dim s As XElement = x.Descendants("status")(0)
        If s Is Nothing Then
            'Logging.Log.
            Return String.Empty
        End If
 
        If s.Value = "OVER_QUERY_LIMIT" And trynumber < 4 Then
            System.Threading.Thread.Sleep(500) ' this sucks but what else can I do, actually it works well with google
            trynumber += 1
            Return GetGoogleLatLng(c, trynumber)
        End If
 
        Dim e As XElement = x.Descendants("geometry").Elements("location")(0)
        If Not e Is Nothing Then
            Return String.Format("{0},{1}", Xml.GetField(e, "lat", 25), Xml.GetField(e, "lng", 25))
        Else : Return String.Empty
        End If
 
    End Function 

 

 

BTW PS...

 

Want intellisense for your google. namespace?

 

I added this to the top of my usercontrol above

 

<%-- 
<% #if (false) %>
 <script src="../_scripts/jquery-1.7.1.min.js" type="text/javascript"></script>
<% #endif %>
--%>
 

Tags:

Javascript | JQuery

Urls are not strings

by MikeHogg 2. September 2012 09:39

The idea occurred to me recently, that in a web project, if I do a ctrl F for "http://" and find anything that wasn't a third party link, then I might want to take a look at the way I perceive Urls.  There are two main concepts that occur to me at the same time.  The first is that my web project should be able to replicate every feature on every environment (with blue moon exceptions) including on my local machine in debug mode.  So easy stuff like every link should work, and I shouldn't have to worry about clicking any links in special places that might take me to, for instance, the production version of the site.  The Second premise for this, is that I remember reading through some .net framework intellisense, and finding that Uri is a full fledged class (not just another string), so the capability exists to do much more with it, and in web/mvc projects, links seem to me to be more than just strings, they are like... method calls if you will, on controller actions.

 

How would I go about using this idea?  On Application Startup, set a static HOSTNAME, PORT, SCHEME, whatever you intend to do with links, from webconfig (post transform), and then craft your Uris with that, handling specific ports (are you setting Cassini to use the same port on debug? there is a use for that).  the UriBuilder let's you craft every part of the url dynamically, instead of having to use one long hard coded string over and over. Wrap the specific functionality you need in a static class (one or two methods like GetSSLUrl(pathname) and GetNonSSLUrl(pathname)) and you can go back to writing one liners for your links, and even use them in razor templates. There is just so much in the Uri class that you should be able to handle all kinds of situations, with strong typed code, and have everything testable, 'inside the box', meaning write it once and never have to worry about it again.  "Will those links work on that page?"  "Will they work on that server?"  "when I change X, when I turn on SSL, when I send them in an email, when I publish to a new build server?"  Promote your hardcoded links to Uri's and - write it once and never have to worry about it again.

 

Migrating to your UAT host should be painless and all the links will now magically point to httporhttpsScheme://myuathost/:optionalport/urlstringhere.whatever.ext and migrating to your production host should be the same.  Want to set up a third environment to test some branch?  Just add the transforms.

Tags:

Architecture

Predicates

by MikeHogg 25. August 2012 12:18

 

 

LINQ is fantastically powerful. It has changed the way I approach almost every problem now in .net, from loosely typed collections like DataTables, and DataRows (only need these for interfaces now), to strong typed classes and IEnumerables, for dynamic filtering and creation of new collections with Lambdas.  Very powerful stuff.

But still I have found myself, a few times, passing a collection to a long list of methods, where in each one I pull a subset of objects that match a certain filter, over and over with each method doing just about the same thing, except for a different filter on the collection.  This time, I wanted to get closer to implementing the dynamic predicate (I think new in 4.0?), in a baby step, by passing the predicate as an argument. 

So where I initially did something like this:

 

 

 

IEnumerable<ACME_WEB.Models.FooEvent> events = ACME_WEB.lib.Repository.GetFooEvents();
 
            List<ACME_WEB.Models.FooEvent> _emailedevents = new List<ACME_WEB.Models.FooEvent>();
            _emailedevents.AddRange(GetFeeReminders(events));
            _emailedevents.AddRange(GetFighReminders(events));
            _emailedevents.AddRange(GetPhoReminders(events));
            _emailedevents.AddRange(GetFunReminders(events));

 

 

 

And then had six-plus methods following, all doing the same thing for different .Where(a=>a.lambdas).  Instead, I replaced it with one method, and the six calls just pass the lambdas:

IEnumerable<ACME_WEB.Models.Foo> foos = ACME_WEB.lib.Repository.GetFoos(); 
    List<ACME_WEB.Models.FooNote> _emails = new List<ACME_WEB.Models.FooNote>();
 
    _emails.AddRange(GetNotes(foos, ACME_WEB.Models.NoteType.Fee,
    new Func<ACME_WEB.Models.Foo, bool>(
            f => f.HasFee == false && f.TypeId == ACME_WEB.lib.CONST.FEETYPEID &&
             f.Events.Count(e => e.EventTypeId == ACME_WEB.lib.CONST.FUNID &&
                            e.StartDate == DateTime.Now.Date.AddDays(10)) > 0)));
 
    _emails.AddRange(GetNotes(foos, ACME_WEB.Models.NoteType.Pho,
    new Func<ACME_WEB.Models.Foo, bool>(
           f => f.HasPho == false && f.TypeId == ACME_WEB.lib.CONST.PHOID &&
            f.Events.Count(e => e.EventTypeId == ACME_WEB.lib.CONST.FUNID &&
                            e.StartDate == DateTime.Now.Date.AddDays(1)) > 0)));
// … and then, the single method
private static List<ACME_WEB.Models.Note> GetNotes(
         IEnumerable<ACME_WEB.Models.Foo> allfoos,  
         ACME_WEB.Models.NoteType notetype,
         Func<ACME_WEB.Models.Foo, bool> predicate)
        {
            var emails = from ACME_WEB.Models.Foo d 
                 in System.Linq.Enumerable.Where(allfoos, predicate)
                         select new ACME_WEB.Models.Note(f, notetype);
                 // and that’s it
}

Tags:

Linq

A More Mature User (model)

by MikeHogg 12. August 2012 09:52

My MVVM and MVC User models have usually been a hierarchy of different classes starting with the simplest name/password and adding more properties with each inherited subclass. 

 

(Side Note: I've noticed a tendency to add more properties to different versions of subclasses can get out of hand, mucking up a membership provider, when mixed with Roles, when what is really called for is a Profile provider for all those descriptive properties.  See Decorate Pattern and MS ProfileProvider.)

 

 

 

This time a client requirement surprised me by making even username/password optional, which I've never done.  As a matter of fact, most of my user hierarchy was built to support the base class of required properties using MVC DataAnnotation Required Attributes.  So

 

I rewrote it as one base User with no requireds, and then various subclasses more like ViewModels.  Also I Interfaced the User and changed all my Membership and Repository arguments to the interface.  Now this pattern seems much more flexible, simple, and easily extensible.  Don't know why I didn't see this before.

 

 

public interface IUserModel
    {
        int Id { get; set; }
 
        string EmailAddress { get; set; }
        string Password { get; set; }  
 
        bool Active { get; set; }
        string FirstName { get; set; }
        string LastName { get; set; }
        string PhoneNumber { get; set; }
        bool GetsEmail { get; set; }
 
        IEnumerable<RoleModel> Roles { get; set; }
    }
    
    [Serializable]
    public class UserModel : IUserModel
    {
        [Key]
        public int Id { get; set; }
 
        [Display(Name = "Email Address")]
        [StringLength(255)]
        [MyLibrary.Web.Mvc3.Attributes.EmailAddress]
        public virtual string EmailAddress { get; set; }
 
        [DataType(DataType.Password)]
        [Display(Name = "Password")]
        public virtual string Password { get; set; }
 
        [Display(Name = "Active")]
        public bool Active { get; set; }
 
        [Display(Name = "First name")]
        [StringLength(50)]
        public virtual string FirstName { get; set; }
 
        [Display(Name = "Last name")]
        [StringLength(50)]
        public string LastName { get; set; }
 
        [StringLength(255)]
        [Display(Name = "Phone Number")]
        public string PhoneNumber { get; set; }
 
        [Display(Name = "Gets Emails?")]
        public bool GetsEmail { get; set; }
 
        [Display(Name = "Roles")]
        public IEnumerable<RoleModel> Roles { get; set; }
    }
 
 
    public class LogOnModel : UserModel
    {
        [Required]
        [Display(Name = "Email Address")]
        [StringLength(255)]
        [MyLibrary.Web.Mvc3.Attributes.EmailAddress]
        public override string EmailAddress { get; set; }
 
        [Required]
        [DataType(DataType.Password)]
        [Display(Name = "Password")]
        public override string Password { get; set; }
 
    }
 
    public class RegisterModel : LogOnModel
    { 
        [Required(ErrorMessage = "Please enter a first name")]
        public override string FirstName { get; set; }
 
        [DataType(DataType.Password)]
        [Compare("Password")]
        [Display(Name = "Confirm Password")]
        public string PasswordConfirm { get; set; }
    }
 
    public class ChangePasswordModel : UserModel
    {
        [Required]
        [DataType(DataType.Password)]
        [Display(Name = "Old Password")]
        public string OldPassword { get; set; }
        
        [Required]
        [DataType(DataType.Password)]
        [Display(Name = "Password")]
        public override string Password { get; set; }
 
        [DataType(DataType.Password)]
        [Compare("Password")]
        [Display(Name = "Confirm Password")]
        public string PasswordConfirm { get; set; }
    }
 
    public class FoundPasswordModel : UserModel
    {
        [Required] 
        public override string EmailAddress { get; set; }
 
        [Required]
        [DataType(DataType.Password)]
        [Display(Name = "Password")]
        public override string Password { get; set; }
 
        [DataType(DataType.Password)]
        [Compare("Password")]
        [Display(Name = "Confirm Password")]
        public string PasswordConfirm { get; set; }
    }
 
    public class ValidatedUserModel : UserModel
    {
        [Required]        [StringLength(255)]
        [MyLibrary.Web.Mvc3.Attributes.EmailAddress]        public override string EmailAddress { get; set; }
 
        [Required(ErrorMessage = "Please enter a first name")]
        public override string FirstName { get; set; }
 
        public ValidatedUserModel() { }
        public ValidatedUserModel(IUserModel user)
        {
            Active = user.Active;
            EmailAddress = user.EmailAddress;
            FirstName = user.FirstName;
            GetsEmail = user.GetsEmail;
            Id = user.Id;
            LastName = user.LastName;
            PhoneNumber = user.PhoneNumber;
            Roles = user.Roles;
        }
    }

Tags:

MVC | Architecture

Best of Datagrid plugins for web

by MikeHogg 12. August 2012 09:43

After researching several datagrid mechanisms for mvc3 web page, looking for powerfule filtering and sorting and paging built in, I went with actually a javascript implementation called SlickGrid.

 

https://github.com/mleibman/SlickGrid

 

There was another js grid actually that got very positive reviews also.  I think there was a SO question asking for reviews but it's been a while since I did this research and I don't remember the name or why I chose this one over the others, or over .net controls.  I just want to document how I used it for the future. It offers powerfully fast sorting paging and even As You Type filtering on datasets upwards of 100k I am told, and believe, although I have not had need to use it for more than an order of hundreds yet.

 

besides adding the source (i use slick.core, .dataview, .formatters (might be mine), and .pager) to your project, you init the grid like any other plugin.  You set your Columns and options.  Columns here is the important point.

 

 
<link href="@Url.Content("~/Content/slick.grid.css")" rel="stylesheet" type="text/css" />
<link href="@Url.Content("~/Content/slick.pager.css")" rel="stylesheet" type="text/css" />
<script src="@Url.Content("~/Content/js/jquery.event.drag-2.0.min.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Content/js/slick.core.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Content/js/slick.grid.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Content/js/slick.dataview.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Content/js/slick.formatters.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Content/js/slick.pager.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Content/js/json2.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Content/js/slick.mylib.js")" type="text/javascript"></script>
 
<script type="text/javascript">
 
    var columns = [
     { id: "Name", name: "Name", field: "Name", width: 140, sortable: true },
                    { id: "Email", name: "Email", field: "Email", width: 140, sortable: true }, 
     { id: "Type", name: "Type", field: "Type", sortable: true }, 
                    { id: "Title", name: "Title", field: "Title", width: 150, sortable: true },
                    { id: "RequestDate", name: "Request Date", field: "RequestDate", formatter: Slick.Formatters.Date, filter: false, sortable: true },
                    { id: "DueDate", name: "Due Date", field: "DueDate", formatter: Slick.Formatters.Date, filter: false, sortable: true },
                    { id: "AnotherDueDate", name: "Another Due Date", field: "AnotherDueDate", formatter: Slick.Formatters.Date, filter: false, sortable: true },
                    { id: "Details", name: "Details", field: "Id", filter: false,
                        formatter: function (row, cell, value, columnDef, dataContext) {
                            return '<a href="/Home/ViewDetails/' + dataContext['Id'] + '">Details</a>';  }   },
 
                    { id: "selector", name: "Has Approval", field: "HasApproval", formatter: Slick.Formatters.Checkmark, cssClass: "centered", filter: false, sortable: true },
                    { id: "HasDeadline", name: "Has Deadline", field: "HasDeadline", formatter: Slick.Formatters.Checkmark, cssClass: "centered", filter: false, sortable: true },
                    { id: "AddFiles", name: "Add Files", field: "HtmlUploads", cssClass: "icons centered", filter: false,
                        formatter: function (row, cell, value, columnDef, dataContext) {
                            return '<span class="add-files" data-for="#add-files-text-' + dataContext['Id'] + '">Add</span>' +
                            '<span id="add-files-text-' + dataContext["Id"] + '" class="add-files-text">' +
                            value + '</span>'; }   },
 
                    { id: "HasCapital", name: "Has Capital", field: "HasCapital", cssClass: "centered", filter: false, sortable: true, formatter: Slick.Formatters.Checkmark },
                    { id: "DownloadFiles", name: "Download", field: "HtmlDownloads", width: 80, filter: false, formatter: function (row, cell, value, columnDef, dataContext) {
                        return '' + value; }   }
                ];
 
 
                var options = {
                    enableColumnReorder: false,
                    forceFitColumns: true,
                    defaultColumnWidth: 60,
                    autoHeight: true,
                    rowHeight: 60,
                    showHeaderRow: true,
                    headerRowHeight: 37
                };
 
</script>   
 
<h2>Welcome to the Mike Hogg Something Manager</h2>
 
<div class="text clearfix">
    <div class="button-cluster centered wide clearfix">
        @Html.ActionLink("Submit New Something", "NewSomething", null, null, new { @class = "btn" })
        @Html.ActionLink("View Calendar", "Index", "Calendar", null, new { @class = "btn" })
    </div>
    <br />
    <p>Below is a list of submitted somethings currently in your queue. To upload a xyz or abc file, select your something and click on the "Add File" icon. You can also sort the somethings by clicking on the column header, or filter by typing in  stuff</p>
</div> 
 
    <input type="hidden" id="message" value="used in getExport() above"/>
    <div id="myGrid"></div> 
    <div id="myPager"></div> 

 

And then in your js you set up your ajax method to get the json that populates the column, and sets the data to a placeholder control on your page.  I think leiberman builds the sort event into the grid, but you need to write your own comparers, so mine is here.  I think the basic filter was included but needs to be extended for your needs.  I had a need for escaping HTML and found the escape map also on S.O.

 

 

 
var grid;
var sortcol = "RequestDate";
var sortdir = -1;
var columnFilters = {};
var dataView = new Slick.Data.DataView({ inlineFilters: true });
dataView.setPagingOptions({ pageSize: 8 });
 
 
// my freshmen filter
function filter(item) {
    for (var columnId in columnFilters) {
        if (columnId !== undefined && columnFilters[columnId] !== "") {
            var c = grid.getColumns()[grid.getColumnIndex(columnId)];
            //if (item[c.field] != columnFilters[columnId]) {
 
            var field = item[c.field], myfilter = columnFilters[columnId];
            if (field != null && myfilter != null) {
                if (field.toString().toLowerCase().indexOf(myfilter.toString().toLowerCase()) == -1) {
                    return false;
                }
            } else { return field == myfilter; }
        }
    }
    return true;
}
 
// my freshmen attempt at comparer (for SORT)
function comparer(a, b) {
    var x = isNaN(a[sortcol]) ? a[sortcol].toLowerCase() : a[sortcol];
    var y = isNaN(b[sortcol]) ? b[sortcol].toLowerCase() : b[sortcol];
    if (x == null || x == "") {
        if (y == null || y == "") {
            return 0;
        }
        else return -1;
    } 
    else if (y == null || y == "") return 1;
    else return (x == y ? 0 : (x > y ? 1 : -1));
}
 
 
 
var entityMap = {
    "&": "&amp;",
    "<": "&lt;",
    ">": "&gt;",
    '"': '&quot;',
    "'": '&#39;',
    "/": '&#x2F;'
};
 
function escapeHtml(string) {
    return String(string).replace(/[&<>"'\/]/g, function (s) {
        return entityMap[s];
    });
}
/// ADDITIONAL

 

We had some cute image links for a popup menu in one of the cells but as you scroll and page through the grid, they needed to be recreated as this grid implementation actually dropped and added rows on and off the dom on the fly, so LoadButtons ...

 

 
// for those pencil icon popup menus for Add Files
function loadButtons() {
 
    $(".add-files-text").dialog({
        autoOpen: false,
        title: "Add Files"
    });
    $(".add-files").button({
        icons: { primary: 'ui-icon-pencil' },
        text: false
    });
    $(".add-files").click(function () {
        $($(this).attr("data-for")).dialog("open");
        return false;
    });
}
 
 
// adds those filters to each column, special here to not add for certain columns, extended by 'filter:'
function updateHeaderRow() {
    for (var i = 0; i < columns.length; i++) {
        var header = grid.getHeaderRowColumn(columns[i].id);
        $(header).empty();
        if (columns[i].id !== "selector" && columns[i].filter != false) {
            $("<input type='text'>")
                    .data("columnId", columns[i].id)
                    .val(columnFilters[columns[i].id])
                    .appendTo(header);
        } else {
            $("<div><span style='height:39px;display:block;'></span></div>").appendTo(header);
        }
    }
    if (grid.getOptions()["excelExport"] == true) {
        // add Excel button
        var header = grid.getHeaderRowColumn(columns[columns.length - 1].id);
        $(header).empty();
        $("<input type='image' src='../Content/img/Excel-icon.png' title='Export to Excel' onclick='getExport();'/>").appendTo(header);
    }
}
 
 
 
function getExport() {
    $.ajax({
        url: '/Home/DeploymentList',
        type: "post",
        data: JSON.stringify(dataView.getFilteredRows()),
        dataType: "json",
        contentType: "application/json; charset=utf-8",
        success: function (result) { getCSVFileFromFormPost(result); },  // ajax post json data which server can read automatically, responds with a string, which ajax then POSTs inside a FORM, to which the server adds headers and returns, so browser can treat it as a download (save/open)
        error: function (xhr, textStatus, errorThrown) {
            $("#message").html('Error occurred, ReadyState: ' + xhr.readyState +
                '; textStatus: ' + textStatus + '; ' + errorThrown);
        }
    });
    return false;
}
 
function getCSVFileFromFormPost(result) {
    $('<form action="/Home/GetCSVFile" method="post"><input type="hidden" name="csv" id="csv" value="' + escapeHtml(result) + '" /></form>').appendTo("body").submit();
}
 

 

The Export to CSV function was a neat one, since we could send the dataset back to the server in json easily, and in MVC we could set up an Action to Deserialize that same json to a List of Models if we lined everything up right.

 

And "the call"...

 

$(function () {
    $.post('/Home/GetDepData', function (data, textstatus) {
 
        grid = new Slick.Grid("#myGrid", dataView, columns, options);
        var pager = new Slick.Controls.Pager(dataView, grid, $("#myPager"));
 
        grid.onSort.subscribe(function (e, args) {
            sortdir = args.sortAsc ? 1 : -1;
            sortcol = args.sortCol.field;
 
            if ($.browser.msie && $.browser.version <= 8) {
                // using temporary Object.prototype.toString override
                // more limited and does lexicographic sort only by default, but can be much faster
 
                dataView.fastSort(sortcol, args.sortAsc);
            } else {
                // using native sort with comparer
                // preferred method but can be very slow in IE with huge datasets 
                dataView.sort(comparer, args.sortAsc);
            }
        });
 
        // wire up model events to drive the grid
        dataView.onRowCountChanged.subscribe(function (e, args) {
            grid.updateRowCount();
            grid.render();
 
            loadButtons();  // when filtered to fewer rows than before and no rows were changed
        });
 
        dataView.onRowsChanged.subscribe(function (e, args) {
            grid.invalidateRows(args.rows);
            grid.render();
 
            loadButtons();  // when filtered or paged
        });
 
        dataView.onPagingInfoChanged.subscribe(function (e, pagingInfo) {
            var isLastPage = pagingInfo.pageNum == pagingInfo.totalPages - 1;
            var enableAddRow = isLastPage || pagingInfo.pageSize == 0;
            var options = grid.getOptions();
        });
 
 
        $(grid.getHeaderRow()).delegate(":input", "change keyup", function (e) {
            columnFilters[$(this).data("columnId")] = $.trim($(this).val());
            dataView.refresh();
        });
 
        // initialize the model after all the events have been hooked up
        dataView.beginUpdate();
        dataView.setItems(data, "Id");  // Id capital I to match my unique model id property
        dataView.setFilter(filter);
        dataView.endUpdate();
 
        updateHeaderRow();
        loadButtons();
    }, "json");
});

 

At the end of the included slick.formatters.js I added a couple of my own, the javascript time function being a keeper...

 

    ...
    function YesNoFormatter(row, cell, value, columnDef, dataContext) {
        return value ? "Yes" : "No";
    }
 
    function CheckmarkFormatter(row, cell, value, columnDef, dataContext) {
        return value ? "<img src='../Content/img/slickimages/tick.png'>" : "";
    }
    function DateFormatter(row, cell, value, columnDef, dataContext) {
        if (value !== null) {
            var d = new Date(parseInt(value.substr(6, 13)));
            return (d.getMonth() + 1) + "/" + d.getDate() + "/" + d.getFullYear() + ' ' + getAMPMTime(d);
        } else return "";
    }
 
    Number.prototype.pad = function (len) {
        return (new Array(len + 1).join("0") + this).slice(-len);
    }
 
    function getAMPMTime(d) {
        var d = new Date(d);
        var hour = d.getHours();
        var min = d.getMinutes().pad(2);
        var ap = "AM";
        if (hour > 11) { ap = "PM"; }
        if (hour > 12) { hour = hour - 12; }
        if (hour == 0) { hour = 12; }
 
        if (hour == 12 && min == 0 && ap == "AM") return '';
 
        return hour + ':' + min + ' ' + ap;
    }

Tags:

JQuery | MVC | Javascript

CheckboxLists in MVC3

by MikeHogg 2. August 2012 09:48

 

MVC does not really model bind checkboxes. The problem is html based; input checkboxes do not post in the form unless actually checked. MVC gets around this with their helper by creating a hidden for every checkbox, and I assume unobstrusively sets the hidden input whenever the associated checkbox is toggled.

 

This means that we cannot validate the checkbox as required, which isn't an easy case anyway since a bool defaults to false. It’s not a nullable type.  And we can't readily access the hidden input to validate that.  People have created several roll-your-own extensions and helpers out there… but I couldn’t find one that fit my needs exactly.

 

My main requirement was a list of boxes (a grouping so to speak) requiring at least one to be checked. With client validation (most examples on the web missed this one).  SO I found myself a little library project.

 

Two parts to this-

  • - [In your Model]: add your list of checkboxes like this: IEnumerable<MH.Mvc.Checkbox> { new {Name="Bank"} new {Name="Stocks", IsChecked?="true"}}
    • decorate your checkboxlist with [MH.Attributes.CheckBoxListServerValidator], and you will get server validation that at least one box is checked
    • In your View, use the HtmlHelper.CheckBoxListFor(m=>m.checkboxlist) which gives you the proper model binding
  • - [In your Model]: add a bool named something like this: CheckboxClientValidator
    • decorate your bool with [MH.Attributes.CheckBoxListClientValidator(listname)], and
    • add the MH/Content/scripts/custom-validators js to your page and you will get client side validation that at least one box is checked

Here is what my Model looks like:

 

 

[MH.Web.Mvc3.Attributes.CheckBoxListClientValidator("LiquidAssetSourceList")]
        public bool LiquidAssetClientValidator { get; set; }    
        [Display(Name = "Where do you have your liquid assets? (Check all that apply.)*")]
        [MH.Web.Mvc3.Attributes.CheckBoxListServerValidator]
        public IEnumerable<MH.Web.Mvc3.Models.CheckBox> LiquidAssetSourceList { get; set; }

 

In my Model constructor (isn't that where all lists should get instantiated?) I create the actual checkboxes

 

 

LiquidAssetSourceList = new List<MH.Web.Mvc3.Models.CheckBox>  
                {   new MH.Web.Mvc3.Models.CheckBox{ Name = "Bank" },
                    new MH.Web.Mvc3.Models.CheckBox{ Name = "Stocks" },

 

... and Here is what my View looks like:

 

@Html.LabelFor(model => model.LiquidAssetSourceList)
@Html.HiddenFor(m=>m.LiquidAssetClientValidator)
@Html.ValidationMessageFor(m=>m.LiquidAssetClientValidator)
@Html.CheckboxListFor(m=>m.LiquidAssetSourceList)
Here's the simple checkbox object:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
 
namespace MH.Models
{
    public class CheckBox
    {
        public int ID { get; set; }
        public string Name { get; set; }
        public bool IsChecked { get; set; } 
    }
}

 

And the HtmlExtension:

  public static System.Web.Mvc.MvcHtmlString CheckboxListFor<TModel, TProperty>(this HtmlHelper<TModel> html,
            Expression<Func<TModel, TProperty>> checkboxlist, object htmlattributes = null)
             where TProperty : IEnumerable<Models.CheckBox>
        {
            MemberExpression body = checkboxlist.Body as MemberExpression;
            string checkboxlistname = body.Member.Name;
 
            //Get currently selected values from the ViewData model
            IEnumerable<Models.CheckBox> cbs = checkboxlist.Compile().Invoke(html.ViewData.Model);
 
            StringBuilder sb = new StringBuilder();
            int idx = 0;
            foreach (Models.CheckBox cb in cbs)
            {
                string id = String.Format("{0}_{1}__IsChecked", checkboxlistname, idx);
                string name = String.Format("{0}[{1}].IsChecked", checkboxlistname, idx);
 
                TagBuilder itemdiv = new TagBuilder("div");
                itemdiv.AddCssClass("checkboxlist-itemdiv");
 
                TagBuilder label = new TagBuilder("label");
                label.AddCssClass("checkboxlist-label");
                label.MergeAttribute("for", id);
                label.InnerHtml = cb.Name;
                itemdiv.InnerHtml += label.ToString(TagRenderMode.Normal);
 
                TagBuilder inputcheck = new TagBuilder("input");
                if (cb.IsChecked) inputcheck.MergeAttribute("checked", "checked");
                inputcheck.AddCssClass("checkboxlist-checkbox");
                inputcheck.MergeAttribute("type", "checkbox");
                inputcheck.MergeAttribute("id", id);
                inputcheck.MergeAttribute("name", name);
                inputcheck.MergeAttribute("value", "true");
                itemdiv.InnerHtml += inputcheck.ToString(TagRenderMode.Normal);
 
                TagBuilder hiddencheck = new TagBuilder("input");
                hiddencheck.MergeAttribute("type", "hidden");
                hiddencheck.MergeAttribute("name", name);
                hiddencheck.MergeAttribute("value", "false");  // input checkboxes only post if checked, so we default to hidden with same id... mvc only takes first input of each id
                itemdiv.InnerHtml += hiddencheck.ToString(TagRenderMode.Normal);
 
                TagBuilder hiddenname = new TagBuilder("input");
                hiddenname.MergeAttribute("type", "hidden");
                hiddenname.MergeAttribute("name", String.Format("{0}[{1}].Name", checkboxlistname, idx));
                hiddenname.MergeAttribute("value", cb.Name);
                itemdiv.InnerHtml += hiddenname.ToString(TagRenderMode.Normal);
 
                TagBuilder hiddenid = new TagBuilder("input");
                hiddenid.MergeAttribute("type", "hidden");
                hiddenid.MergeAttribute("name", String.Format("{0}[{1}].ID", checkboxlistname, idx));
                hiddenid.MergeAttribute("value", cb.ID.ToString());
                itemdiv.InnerHtml += hiddenid.ToString(TagRenderMode.Normal);
 
                sb.Append(itemdiv.ToString(TagRenderMode.Normal));
                idx++;
            }
 
            TagBuilder div = new TagBuilder("div");
            div.AddCssClass("checkboxlist-paneldiv");
            div.MergeAttribute("id", checkboxlistname);
            div.MergeAttribute("name", checkboxlistname);
            div.MergeAttributes(new System.Web.Routing.RouteValueDictionary(htmlattributes));
            div.InnerHtml = sb.ToString();
 
            return new System.Web.Mvc.MvcHtmlString(div.ToString(TagRenderMode.Normal));
        } 

The attribute for the validator:

 

Namespace Attributes{
[AttributeUsage(AttributeTargets.Property)]  
    public class CheckBoxListServerValidator : ValidationAttribute 
    { 
        protected override ValidationResult IsValid(object value, ValidationContext validationContext)
        {
            if (value != null) 
            {  
                if ((value as IEnumerable<Euro.Web.Mvc3.Models.CheckBox>).Any(l => l.IsChecked)) return ValidationResult.Success;
            }
            return new ValidationResult("Please select an option.");
        }         
    }
    [AttributeUsage(AttributeTargets.Property)]  // validon bool? i dont think it matters
    public class CheckBoxListClientValidator : ValidationAttribute, IClientValidatable
    {
        public string ListName { get; set; }
        public CheckBoxListClientValidator(string listname)
        {
            ListName = listname;
        }
        protected override ValidationResult IsValid(object value, ValidationContext validationContext)
        { 
            return ValidationResult.Success; 
        }
        public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
        {
            ModelClientValidationRule rule = new ModelClientValidationRule()
                {
                    ValidationType = "requiredcheckbox", // "requiredcheckbox" used in jquery
                    ErrorMessage = "Please select at least one"
                };
            rule.ValidationParameters.Add("listname", ListName);
            return new[] { rule };
        }
    }

and the js for the validator:

In custom-validators.js
/// (do not put validators inside document.ready)
/// these two are for MH.Attributes.CheckBoxListClientValidator 
$.validator.addMethod("requiredcheckbox",
                function (value, element, parameters) {
                    return $("#" + parameters.listname + " input:checked").length > 0;
                });
$.validator.unobtrusive.adapters.add("requiredcheckbox", ["listname"], function (options) {
    options.rules["requiredcheckbox"] = options.params;
    options.messages["requiredcheckbox"] = options.message;
}); 

Tags:

MVC

Static Repositories (vs Instance)

by MikeHogg 30. July 2012 10:10

I find for small projects that I always used to create static repositories.  It can be a speedy mechanism to develop with, and I probably took the lead from MS' Membership classes and their treatment of Membership classes, plus years of working against Oracle databases when no .Net ORM existed that would go against Oracle...

 

I like static because I use them to return typed collections, and they, with linq if necessary, provide easy one liners in most cases where I interface with data access, and don't need to cache or watch my calls for performance, like in Web sites. But, in web cases, note that on each POST or GET, you are recreating all objects anyway, so I would just add predicate calls to static repo.  Here's what many of my repo Getxxx methods look like:

 

 

        internal static IEnumerable<Models.FileModel> GetFileModels(string username = null)
        {
            List<SqlParameter> parms = new List<SqlParameter>();
            parms.Add(new SqlParameter("@EmailAddress", username)); 
 
            IDataReader r = DatabaseHelper.GetDataReader("sp_GetFileModels", parms);
            DataTable t = new DataTable();
            t.Load(r);
 
            var result = from DataRow row in t.Rows
                         select new Models.FileModel
                         {
                             Id = Convert.ToInt32(row["Id"]),
                             RequestDate = DateTime.Parse(row["RequestDate"].ToString()),
                             TitleName = row["TitleName"].ToString(),
                             SomethingId = Convert.ToInt16(row["SomethingId"]),
                             SomethingTypeId = Convert.ToInt16(row["SomethingTypeId"]),
                             FileName = row["Name"].ToString(),
                             FileTypeId =  Convert.ToInt32(row["FileTypeId"]),
                              FileTypeName = row["FileTypeName"].ToString(),
                              ContentEncoding = row["ContentEncoding"].ToString(),
                              ContentLength = Convert.ToInt32(row["ContentLength"]),
                             ActiveFlag = Convert.ToBoolean(row["ActiveFlag"]),
                             EnabledFlag = Convert.ToBoolean(row["EnabledFlag"]),
                             SomeCompanyName = row["SomeCompanyName"].ToString(),
                             SomeNumber = row["SomeNumber"].ToString(),
                             EstimatedNumberOfSomethings = MH.lib.DatabaseHelper.ConvertDBNullsToNInt(row["EstimatedNumberOfSomethings"]),
                             EstimatedDeadline = Convert.IsDBNull(row["EstimatedDeadline"]) ? new DateTime() : DateTime.Parse(row["EstimatedDeadline"].ToString()),
                             FinalDeadline = DateTime.Parse(row["FinalDeadline"].ToString()),
                             ActivityDate = DateTime.Parse(row["ActivityDate"].ToString()),                               
                         }; 
            return result; 
        }

 

 

And then in my business layer I simply Getxxx and if I need a single or a filtered set I simply add a linq predicate.

 

 

model = lib.Repository.GetFileModels(UserNameOrNullIfAdmin()).FirstOrDefault(f =>
                    f.SomethingId == somethingid &&
                    (f.FileTypeId == filetypeid || ( filetypeid == 0 && f.SomethingTypeId == lib.CONST.ACONSTANTID &&  // in case of xyz filetypeid arg is zero
                                                        (f.FileTypeId == lib.CONST.SOMECONSTANTID || f.FileTypeId == lib.CONST.ANOTHERCONSTANTID)))
                    && f.ActiveFlag == true);
 
                if (model == null)
                {
                    Models.Something s = lib.Repository.GetSomethings(UserNameOrNullIfAdmin(), somethingid).FirstOrDefault();
 
                    model = new Models.FileModel
                  {
                      SomethingId = somethingid,
etc., etc.

 

I know this isn't pushing my predicate all the way to the database like Entity Framework so it is something to watch for.  In my experience, most small cases can get to the order of several hundreds of business objects and only require light attention to performance.  And this handles most non-business related applications.

 

But most of the MVC framework codebases and examples I have seen out there that is shared or posted on blogs, happen to use the instance repository pattern... And I hate not knowing Why something is the way it is, so … what are the reasons NOT to use static Repos?

 

I searched for a while, but I only found maybe one informative post, with a couple good answers.

http://stackoverflow.com/questions/5622592/why-arent-data-repositories-static

 

It makes a lot of sense.  Instantiated Repositories can be mocked, and your front layers tested.  That is the number one reason.  I'm not sure that instantiation is a requirement for caching, but it makes sense if you are getting into a site that large.  Second reason – for me this is a big one – is that you can use the inheritance and interfaces to practice DRY and the Repository pattern and not have to write all of your boilerplate data access code in each repository. 

 

The first point speaks to testing, and I haven't seen or heard of any local developers who work at a place that actually promote unit tests.  But I do on occasion in some of my projects, and it helps my writing style.  Food for thought...

Tags:

Architecture | C#

JQuery

by MikeHogg 5. July 2012 09:49

 

On my third web project, I got the opportunity to dive into jquery a little more.   We were rewriting an existing site in MVC3.  One of my tasks was to reproduce a standard sort of “Locations” page.  I had some javascript to work from, but we were adding a new “Location Features” feature, and a GPS feature, and the existing javascript wasn’t in any shape to be extended.  I only needed a small form, and my only server side code, my Location Features MVC3 call, was a three liner LINQ query against an Entity Framework db, so most of the task was my opportunity to rewrite some old mess of long cryptic javascript function into jquery, and figuring out a good readable, maintainable code flow.    In the first page here you will see my standard style of using the MVC model binding, even for two properties, and a very short clean page of html, with my CSS file referenced in the beginning, and my JS file referenced at the end.

 

 

 

@model SomeBase.Models.LocatorModel
<link rel="stylesheet" href="@Url.Content("~/Content/css/locator.css")">
<div class="locatordiv">
    @using (Html.BeginForm("Locator", "Home", FormMethod.Post, new { id = "locator" }))
    {   
        <p>  Enter a city and state, or zip code below.</p>
                    
        @Html.HiddenFor(m => m.Gps)
        @Html.TextBoxFor(m => m.Zip, new { title = "Enter ZIP", @Class = "textbox" })
        
        <input type="submit" value="" class="findbutton" data-category="Find" data-event="Homepage Zipcode" />
        <a href="#" class="gpsbutton" onclick="do_geo();" title="GPS" data-category="Find" data-event="GPS"></a>
     
        <div id="gpsloading">Working ...<br /><img src="@Url.Content("~/Content/images/load-bar.gif")" /></div>     
    }  
</div>
<div id="maploading">
    <img src="@Url.Content("~/Content/images/load-circle.gif")" />
</div>
<div id="results"></div>     
<div id="mapDiv"></div>
    
<script type="text/javascript">
            var configMQAPIKey = '@Html.Raw(System.Configuration.ConfigurationManager.AppSettings["MQ:APIKey"].ToString())';
            var configMQOLOKey = '@Html.Raw(System.Configuration.ConfigurationManager.AppSettings["MQ:OLOKey"].ToString())';
            var configMQHostedDataTable = '@Html.Raw(System.Configuration.ConfigurationManager.AppSettings["MQ:HostedDatatable"].ToString())'; 
        </script>
        <script src='//www.mapquestapi.com/sdk/js/v7.0.s/mqa.toolkit.js?key=@Html.Raw(System.Configuration.ConfigurationManager.AppSettings["MQ:APIKey"].ToString())'></script>
        <script src="@Url.SomeContent("~/Content/js/locator.js")"></script>
        <script type="text/javascript" src="@Url.Content("~/Content/js/jquery.cookie.js")" ></script> 

The javascript wasn’t terribly complicated, but it was fun to get deeper into jquery and learn to use standard jquery element creation and manipulation methods, and the unobstrusive javascript pattern, as opposed to the old verbose javascript getelementById calls.   Here you will see my style of paying special attention to method and variable names, in place of comments.  I was taught that comments should be for WHY not WHAT, and if you name your objects clearly enough, you don’t need to comment WHAT you are doing.

 

 

function searchByGps() {
    $("#Zip").val(""); $("#Key").val("")
    search("https://www.mapquestapi.com/search/v1/radius" +
                    "?key=" + mq_key + "&radius=" + $("#inradius").val() + "&callback=processPOIs&maxMatches=" + inmatch +
           "&origin=" + encodeURIComponent($("#Gps").val()) + "&hostedData=" + intable);
} 
function searchByZip() {
    $("#Gps").val(""); $("#Key").val("")
    search("https://www.mapquestapi.com/search/v1/radius" +
                    "?key=" + mq_key + "&radius=" + $("#inradius").val() + "&callback=processPOIs&maxMatches=" + inmatch +
           "&origin=" + encodeURIComponent($("#Zip").val()) + "&hostedData=" + intable);
} 
                
function search(url){
    MQA.IO.doJSONP(url); 
}
function processPOIs(results) {
    if (results.searchResults != null && results.searchResults.length > 0) {
        drawPOIsOnMap(results.searchResults);
        drawResultTable(results.searchResults);
        
        if ($("#Key").val()) {
            $("#Zip").val(results.searchResults[0].fields.address + " " + results.searchResults[0].fields.city + ", " + 
                          results.searchResults[0].fields.state + " " + results.searchResults[0].fields.postal); 
        }
        $.ajax('/Home/GetLocationFeatures', {
            data: JSON.stringify(parseToEntityObjects(results.searchResults)),
            dataType: "json",
            type: "post",
            contentType: "application/json",
            success: function (featuredata) { processFeatures(featuredata, results.searchResults); }
        });
    } 
    else if (results.info && results.info.statuscode == 610) {
        // ambiguities
        // results.collections[1] is To, 0 is From
        $("#Gps").val(results.collections[0][0].latLng.lat + ',' + results.collections[0][0].latLng.lng);
        searchByGps();
    }
    else {
        $('#results .frame').html("<h1>Oops! We couldn’t find any results. Please try your search again.</h1>");
    }
}
function processOLOs(oloData, searchResults) {
    $.each(searchResults, function (i, result) { 
        if (oloData != null) {
            $.each(oloData.restaurants, function () {
                if (this.telephone == result.fields.Phone) {
                    var oloid = '#olo' + result.fields.RecordId;
                    $(oloid).append($("<a></a>", { href: this.url, "class": "ololinks", target: "_blank", text: "Place an Order" }));
                }                    
            });  }   }); }  
function processFeatures(featureData, searchResults) {
    $.each(searchResults, function (i, result) { 
        if (featureData != null) {
            $.each(featureData, function () {
                if (this.StoreNumber == result.fields.RecordId) {
                    var fid = '#feature' + result.fields.RecordId;
                    $(fid).append($("<a></a>", {href:this.Url, "class":"featurelinks", target:"_blank", text:this.Label}));
                }   });         }  });  }   
function drawResultTable(results) {
    $("#maploading").hide();
    $('#results').html("<h1>Search Results</h1>");
         
    $.each(results, function (i, result) {
            
        $("<div></div>", { "class": 'resultrow' })
                     .append("<p class='addressrowresult'>" +
                result.fields.address + "<br/ >" +
                result.fields.city + ", " + result.fields.state + "<br/ >" +
                result.fields.Phone + "<br /></p>")
            .append("<div>" + getRoundedDistance(result) + " Mi.    " + getMapItLink(result) + "</div>") 
            .append("<div class='olos' id='olo" + result.fields.RecordId + "'></div>")   // olo can find this span$(#olo#storenumber#) later
            .append("<div class='features' id='feature" + result.fields.RecordId + "'></div>") // feature can find this span$(#feature#storenumber#) later
            .appendTo("#results");
    }); }  
function parseToEntityObjects(data) {
    var locations = [];
    $.each(data, function () {
        locations.push({
            StoreNumber: this.fields.RecordId,
            Address1: this.fields.address,
            City: this.fields.city,
            ZipCode: this.fields.postal,
            Phone: this.fields.Phone
        });
    });
    return locations;
}
function getRoundedDistance(result) { 
    if (result.distance > 10) {
        return Math.round(result.distance);
    } else if (result.distance > 1) {
        return Math.round(result.distance * 10) / 10;
    }  
    return Math.round(result.distance * 100) / 100; 
}
function getMapItLink(result){ 
    var destination = result.fields.address + ',' + result.fields.city + ',' + result.fields.state + ',' + result.fields.postal;
    var link = $("<a>", { href: "#", "class": "mapitlink", title: "Map It!", onclick: "mapIt(getOrigin(), '" + destination + "');return false;", text: "Map It!" })
                .attr({ "data-category": "Find", "data-event": "Map Quest" });
    return $('<div>').append(link.clone()).html();  // hack to get string not js object for mqa
}                    
function getPOIRollover(result) {
    var rollover = $("<div></div>", { "class": "poirollover" }).append($('<h4></h4>').text(result.fields.N))
        .append($('<span></span>').text(result.fields.address))
        .append($('<br />')).append($('<span></span').text(result.fields.city + ", " + result.fields.state))
        .append($('<br />')).append($('<span></span>').text(result.fields.Phone))
        .append($('<br />')).append($('<span></span>').text(getRoundedDistance(result) + " Mi.  "))
        .append(getMapItLink(result));
    return $('<div>').append(rollover.clone()).html(); // hack to get string not js object for mqa
}
function mapIt(origin, destination) {
    // build a url and send to the Directions Web Service
    MQA.IO.doJSONP("http://www.mapquestapi.com/directions/v1/route?" +
              "key=" + mq_key + "&" +
              "from=" + origin + "&" +
              "to=" + destination + "&" +
              "shapeFormat=raw&generalize=0.1&" +
              "callback=drawDirections");
}

 

My CSS file was mostly empty at the start.  But I wanted to make it easy to add style later, by applying classes and these empty placeholders for just about every element that we might want.

 

 

.locatordiv
{
    text-align:center; 
    margin:20px;
} 
.gpsbutton {
       background:url(../images/icon-gps.png) no-repeat;      
       background-size:42px auto; 
       padding:21px;    
} 
#gpsloading
{    display:none;
}
#maploading
{     display: none;  
}
#mapDiv
{    float:left; 
}
#results
{    float:left;
}
.resultrow
{    margin:20px;
}
.mapitlink
{    
}
.poirollover
{    width:150px;
}
.olos
{
}
.ololinks
{
}
.features
{    display:block;
}
.featurelinks
{    display:block;
}
.addressrowresult
{    width: 210px;
}
#directionsdiv
{    
}
.directionrow
{    
}

Tags:

Javascript | JQuery

MVC

by MikeHogg 25. June 2012 10:06

 

In my second or third MVC3 project, I have really enjoyed Model Binding and my familiarity with the MVVM pattern in WPF translated well.  Web projects are much more enjoyable in c# for me than they were in the old webforms days.  Additionally, I found features in c# 4.0 that I really like, and frequently find myself taking advantage of nullable parameters, and lambda expressions.  I was asked to replicate legacy code in MVC3, which simply retrieved a feed of xml from a web service and turned them into a searchable data store of links.  Caching was a new feature of this.  The number of links was relatively small enough, that I decided to use file, not database, for the store, and simply creating my data model class with simple .net types allowed me to save my models in Properties.Settings.   In this code sample you will see the model, including a list of IEnumerable Headlines of either type of an enum, since we use this to serve up actually two different pages- InTheNews for public third party publications (WBAL, Women’s World) and NewsReleases (company generated PR)…

 

 

 

namespace SomeBase.Models
{
    public enum NewsType { InTheNews, NewsReleases };
    public class NewsModel
    {
        public string Title { get { return (NewsType == NewsType.InTheNews) ? "In The News" : "News Releases"; } set { var x = value; } }
        public NewsType NewsType { get; set; }
        public DateTime LastUpdateDate { get; set; }
        public IEnumerable<Headline> Headlines { get; set; }
        public string SearchString { get; set; }
        public string SearchType { get; set; }
        public List<System.Web.Mvc.SelectListItem> SearchTypes { get; set; }
        [DisplayFormat(DataFormatString = "{0:" + MH.Web.Mvc3.lib.CONST.DATEFORMATSTRING_SHORT + "}", ApplyFormatInEditMode = true)]
        public DateTime? SearchStartDate { get; set; }
        [DisplayFormat(DataFormatString = "{0:" + MH.Web.Mvc3.lib.CONST.DATEFORMATSTRING_SHORT + "}", ApplyFormatInEditMode = true)]
        public DateTime? SearchEndDate { get; set; }
         
    }
    public class Headline
    {
        [DisplayFormat(DataFormatString = "{0:" + MH.Web.Mvc3.lib.CONST.DATEFORMATSTRING_SHORT + "}")]
        public DateTime PostDate { get; set; }
        public HtmlString Link { get; set; }
        public NewsType NewsType { get; set; }
        public string ArticleType { get; set; }
    }
}

In the Controller, I use an abstract class because this might be served from the Desktop Web Site, or another project, for the Mobile site.  Most of the functionality is in the abstract controller, but the final  controller must implement the Save and Load functions of whatever data store they use, in our case the Properties.Settings.   Here I get to use the new Xml.Linq libraries which make loading an xml document from a url and parsing it one liners…

 

 

private Models.NewsModel GetFilteredNews(Models.NewsModel news)
        {
            Models.NewsModel allnews = GetAllNews(news.NewsType);
            news.SearchTypes = allnews.SearchTypes; // recreate dropdownlist- lost on postback?
            news.Headlines = allnews.Headlines
                .Where(h => h.NewsType == news.NewsType &&
                    (news.SearchEndDate == null || h.PostDate < news.SearchEndDate) &&
                    (news.SearchStartDate == null || h.PostDate > news.SearchStartDate) &&
                    (news.SearchType == null || news.SearchType == h.ArticleType) &&
                    (String.IsNullOrEmpty(news.SearchString) || h.Link.ToString().ToLower().Contains(news.SearchString.ToLower()))
                    ).OrderByDescending(h => h.PostDate);
            return news;
        }
        private Models.NewsModel GetAllNews(Models.NewsType newstype)
        {
            Models.NewsModel news = GetSavedNews();
            if (news.LastUpdateDate < DateTime.Now.AddDays(-1))
            {
                // get feed from vocus 
                Models.Headline[] latest = GetVocusFeed();
                if (latest.Count() > 0)  // if vocus is down?
                {
                    news.Headlines = latest;
                    news.LastUpdateDate = DateTime.Now;
                    SaveNews(news);
                }
            }
            // process for the view                
            news.NewsType = newstype;
            news.SearchTypes = news.Headlines.Where(h=>h.NewsType == newstype && !String.IsNullOrEmpty(h.ArticleType))
                .OrderBy(h => h.ArticleType).Distinct()
                .Select(h => new System.Web.Mvc.SelectListItem
                {
                    Text = h.ArticleType,
                    Value = h.ArticleType
                }).ToList();         
            return news;
        }
        private Models.Headline[] GetNewsFeed()
        {
            List<Models.Headline> headlines = new List<Models.Headline>();
            System.Xml.Linq.XDocument collateral = System.Xml.Linq.XDocument.Load(
http://somenewsfeed.com/Custom/CustomXmlFeed.aspx?something=something);
            System.Xml.Linq.XDocument news = System.Xml.Linq.XDocument.Load(
                http://somenewsfeed.com/Custom/CustomXMLFeed.aspx?somethingelse=something);
            headlines.AddRange(news.Descendants(System.Xml.Linq.XName.Get("NewsResults")).Select(e => new Models.Headline
            {
                ArticleType = GetField(e, "News_MediaOutletSortName"),
                Link = GetLink(e, "News"),
                NewsType = Models.NewsType.InTheNews,
                PostDate = DateTime.Parse(GetField(e, "News_NewsDate"))
            }).Where(e => e.Link != null));
            headlines.AddRange(collateral.Descendants(System.Xml.Linq.XName.Get("SomeResults")).Select(e => new Models.Headline
            {
                ArticleType = GetField(e, "A_Name"),
                Link = GetLink(e, "ALink"),
                NewsType = Models.NewsType.NewsReleases,
                PostDate = DateTime.Parse(GetField(e, "PublishDate"))
            }).Where(e => e.Link != null));
            return headlines.ToArray();
        }
 

 

 

The html page is simple enough, with clean model binding and the html helpers.  Note the handy “If” MvcHtmlString extension and the use of templating for Headlines. 

 

 

<h2 style="margin:30px;">@Html.DisplayFor(m=>m.Title)</h2>
@using (Html.BeginForm())
{
    
        @Html.ActionLink("Request Customized News", "RequestCustomizedNews").If(Model.NewsType == SomeBase.Models.NewsType.NewsReleases)
        <div style="float:left; width:200px; display:block;">
            <h3 >Search Archives</h3>
            <div class="display-label">Headline</div>
            <div class="display-field">
                @Html.EditorFor(model => model.SearchString)
            </div>
             
            <div class="display-label">Type</div>
            <div class="display-field">
                @Html.DropDownListFor(m=>m.SearchType, Model.SearchTypes, "All")
            </div>
            <div class="display-label">From:</div>
            <div class="display-field"> 
                @Html.TextBoxFor(model => model.SearchStartDate, new { @class = "calendar", @Value=Model.SearchStartDate.HasValue? Model.SearchStartDate.Value.ToString(MH.Web.Mvc3.lib.CONST.DATEFORMATSTRING_SHORT): null })
            </div>
            <div class="display-label">To:</div>
            <div class="display-field">
                @Html.TextBoxFor(model => model.SearchEndDate, new { @class = "calendar" })
            </div>
            <input type="submit" value="Go" />
        </div>
        <div style="float:left;">
            <h3>Recent Articles</h3>
            <div>
                @Html.DisplayFor(m => m.Headlines)
            </div> 
        </div>
    </div>
}
I am able to serve this one page “News” as two different Urls- InTheNews and NewsReleases, because of the enum, and MVC’s routing mechanism, which allows me to add a custom route, with a custom constraint.  (I have become a DRY fan, and both pages were nearly identical as it was, so I could hardly bear to repeat the code of two Views, two controller Gets, two controller Posts.)  I would make this more dynamic the next time I touched it by iterating through an enum parameter instead of passing hardcoded values but it was a neat exercise nonetheless…

 

 

 

{Global.asax}

            // Abouts
            routes.MapRoute(
                "About", // Route name
                "About/{newstype}", // URL with parameters
                new { controller = "About", action = "News" }, // Parameter defaults
                new { newstype = new OptionalConstraint(new string[]{"InTheNews", "NewsReleases"}) },
                new string[1] { "Some.Controllers" }
            );
public class OptionalConstraint : IRouteConstraint
    {
        string[] _mappedactions;
        public OptionalConstraint(string[] mappedactions)
        {
            _mappedactions = mappedactions;
        }
        public bool Match(System.Web.HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection)
        {
            if (routeDirection == RouteDirection.IncomingRequest)
            {
                return values[parameterName] == UrlParameter.Optional || _mappedactions.Contains(values[parameterName].ToString(), StringComparer.OrdinalIgnoreCase);
                
            }
            return false;
        }
    }

Tags:

MVC

About Mike Hogg

Mike Hogg is a c# developer in Brooklyn.

More Here

Favorite Books

This book had the most influence on my coding style. It drastically changed the way I write code and turned me on to test driven development even if I don't always use it. It made me write clearer, functional-style code using more principles such as DRY, encapsulation, single responsibility, and more. amazon.com

This book opened my eyes to a methodical and systematic approach to upgrading legacy codebases step by step. Incrementally transforming code blocks into testable code before making improvements. amazon.com

More Here