Sunday, September 29, 2013

Telerik Kendo Grid - What's the drama behind putting a link a grid's cell

After a few hours I was about the end of my rope playing with Telerik's Kendo Grid control.  Seriously...how hard could it be to insert a link into grid that will generate an action, e.g. one of the CRUD operations?  Here's how I finally managed this trick.

The MVC 4 standard approach is rather easy and straight forward.  Really all that is necessary is to post a bunch of Html.ActionLink methods as illustrated in this code snip below.

 @foreach(OfferModel offerModel in ViewData.Model.Offers)  
 {  
   <tr>  
    <td>@Html.DisplayFor(m=> offerModel.Id)</td>  
    <td>@Html.DisplayFor(m=> offerModel.Title)</td>  
    <td>@Html.DisplayFor(m=> offerModel.Description)</td>  
    <td>@Html.DisplayFor(m=> offerModel.Status)</td>  
    <td>  
      @Html.ActionLink("Edit", "Edit", "Offer", new { id=offerModel.Id}, new {@class="edit_button})  
      @Html.ActionLink("Delete", "Delete", "Offer", new { id=offerModel.Id}, new {@class="delete_button})  
      @Html.ActionLink("Copy", "Copy", "Offer", new { id=offerModel.Id}, new {@class="copy_button})  
    </td>  
   </tr>  
 }  

There's a bit more going on here than simply having the action link's place a URL in my table.  I added a class so that these links appear like buttons (standard stuff from jQuery, nothing exciting).

What you end up with is displayed above when the "buttons" are clicked the appropriate controller method is invoked along with the Id so that the right record is either edited, deleted, or copied.

Then there's the Kendo grid - which honestly I'm impressed with.  No having to worry about cross browser compatability, build in sort, filter, and a lot more makes it worth the trouble of trying to figure this out.

There's a lot of postings around putting a link into a Kendo grid.  Some of the ideas presented were pretty decent - sadly they didn't work for me and likely left a few people scratching their heads.  The biggest problem I needed to solve was to be able to embed the "id" of the row in the Html.ActionLink so that the resulting URL would look something like: /Offers/Edit/<id>.

How I got this work was to insert a client template as shown below.

@(Html.Kendo().Grid(Model)  
    .Name("OfferModelGrid")  
    .Columns(columns =>  
    {  
      columns.Bound(p => p.Id);  
      columns.Bound(p => p.Title);  
      columns.Bound(p => p.ShortDescription);  
      columns.Bound(p => p.Status);  
      columns.Bound(p => p.StartDate);  
      columns.Bound(p => p.EndDate);  
   
      columns.Bound(p => p.Id)  
        .Filterable(false)  
        .Title("Action")  
        .Template(@<text></text>)  
        .ClientTemplate(Html.ActionLink("Edit", "Edit", "Offer", new {id = "#=Id#"}, new {@class = "edit_button"}).ToHtmlString() +  
                Html.ActionLink("Delete", "Delete", "Offer", new {id = "#=Id#"}, new {@class = "delete_button"}).ToHtmlString() +  
                Html.ActionLink("Copy", "Copy", "Offer", new {id = "#=Id#"}, new {@class = "copy_button"}).ToHtmlString());  
    }  
    )  
    .Pageable()  
    .Sortable()  
    .Filterable()  
    .DataSource(datasource => datasource.Ajax().Read(read => read.Action("FetchOffers", "Offer")))  
    )  

A number of examples suggested putting a Html.ActionLine between the <text></text> elements - however this didn't seem to affect much, if anything in the results.  The key piece really is the population of the data that needed to be part of the action's URL - namely the #=Id# you see in the forth parameter.  What this is really doing is taking the Id property of the Model associated with the page and placing it into the resulting action link.  The result is exactly what you need - a link which will invoke the indicated method in the indicated controller.  Add in a bit of class and you end up something that looks remarkably like the original MVC screen.


Saturday, September 28, 2013

Interested in returning to software development?

I've been asking myself the same thing periodically.  I've even had a few interviews for a software engineer position where I've been ask "why do you want to return to software development?".  While I won't know the outcome of the most recent interviews - I can illustrate how I avoided ruining my chances at returning to the core of my profession.

Don't Downplay Your Management Skills
Be proud of your accomplishments while at the helm.  Be honest with the challenges and outline both things you'd like to have done as well as the things you could have done better as the manager.  Also point out what appealed to you about management in the first place.  Provide examples of how you showed leadership and what your thoughts are on leadership.  I for instance don't believe that management and leadership are the same things.  Leaders aren't necessary managers - however others respect, listen to, and follow leaders regardless of titles.  Managers can be leaders - if they are doing it right.  Generally managers are culled from the herd because they are the leaders within the development team.

Outline Your Management Philosophy
These things will help the hiring manager know what you are like as well as what you expect from them as a manager (that is if they are a good manager).  Stating that I am not a micro-manager - is bold, but it also makes it clear you don't expect or need to be micro-managed.  State how you view leadership and management - in positive terms.  For instance I look at management as an opportunity to serve the people I work with.  I find their strengths and exploit them.  I find what makes them happy about their job and attempt to give them more of those types of opportunities.  I ignore their weaknesses and try not to make them strengths (yes, because their weaknesses are their problems, not yours or your organizations).  I praise in public and reward discreetly as is needed for the person's personality.  I support them in their job.  And unless they exhibit behavior that needs to be handled by HR - I support them publicly and in private meetings with manager/director peers.  I chasten  and coach in private and (mostly) without any anger.  I remember my place - I will never look good unless the people I serve are happy and productive.

Be Clear On What Can Do and What Your Weaknesses Are
I am good at back end work.  I can put together an API.  I can also write code against and provide a protective layer against someone else's API.  During my stint as a software engineer my second manager would ridicule my mad skills on UI design - more than a few times and mostly in public.  He's a good friend, and I'm sure he didn't have any malice regarding this comments.  Sadly those comments stuck with me - and I am pathetic at HTML and CSS because I've never learned or bothered to learn how to make something look nice.  But remember when I stated above that I leveraged other people's strengths?  So I've relied on a web designer to clean up my look and feel once the core functionality was in place.

Bottom line to all of this? Don't lie and accentuate what you can bring to an organization.


Friday, September 20, 2013

MVC 4 - multiple image/file uploads and viewing images

Having tinkered with MVC 4 in Visual Studio 2012 I hadn't really developed an application that solved enterprise requirements.  Finally an opportunity at work came my way and provided an opportunity to use the MVC design pattern and its implementation in Visual Studio 2012.

The business requirements were rather easy.  The marketing team desired to offer rewards to our subscribers.  These rewards could be a coupon for a discounted yogurt at a local business or even free USB thumb drive with the company logo prominently displayed.

Outside of having to store the basics (e.g. titles, descriptions, start, and end dates) required for this project it was required that different types of images be stored and then later displayed.  The first image was smaller and its primary purpose was to display a small graphic of the company logo that provided the offer.  A larger image could also be added which contains a coupon or other image which needed to be displayed when the offer was redeemed by the customer.

So the first two problems arose - I wanted to be able to upload and display both images on the same form.

The image display actually was rather easy.

<div class="editor-label">  
   @Html.LabelFor(m => m.ImageId)  
 </div>  
 <div class="editor-field">  
   <img id="offerImage" class="imagePreview" src="@Url.Action("GetImage", "Offer", new {id = Model.ImageId, imageType="Image"/>  
   <input type="file" name="offerImage" />  
 </div>  
   

The code do this in the cshtml example above.  By embedding a @Url.Action in the src tag of an image element the server will invoke the method GetImage within the OfferController class.  There is also some opportunity to pass specific parameters so that each image element will display the correct type of image associated with the offer.

What wasn't as intuitive was what type of ActionResult needed to be returned by the OfferController class.  After some hunting around and experimentation I ran into the File function which uses as parameters the image (in a byte[] array) and the MIME type as illustrated below.

 public ActionResult GetImage(int id, string imageType)  
 {  
   DisplayImageModel displayImageModel   
    = _rewardsRepository.GetDisplayImageModel(id, imagetype);  
     
   if ( displayImageModel!=null )  
   {  
    return File(displayImageModel.ImageBytes, displayImageModel.ImageMimeType);  
   }  
   return HttpNotFound();  
 }  

Once that was squared away I was able to successfully display images from my data source.


The real the real trouble came with desire to upload more than one image on the same cshtml form.  There doesn't seem to be anything built into the MVC 4 implementation for this type of operation.  The first thought was to simply adjust expectations and create link that would collect these images in a separate view.  This really didn't provide the polished experience desired by the marketing team and it felt amateurish.

In order to upload any files during the HTTP POST a small change needs to take place in the cshtml file's BeginForm declaration.  The key needed for this is to ensure that enctype of "multipart/form-data" is set as shown below.

 @using (Html.BeginForm("Edit", "Offer", null, FormMethod.Post, new ( offerModel = ViewData, enctype="multipart/form-data"}))  
 {  
   @Html.ValidationSummary(true);  

For those used to ASP.NET and HTML this should be easy for you to grasp why this was needed.  However, as the problem was researched further a number of sites indicated that the method invoked when the html form posted needed to have an IEnumerable<HttpPostedFileBase> parameter.  As it turns out this wasn't at all correct.  In my experience this parameter was always NULL.  Anyone making this statement clearly hasn't actually run their 'example' code.  It didn't seem if any built in parameter would provide what was needed in order to peel off the files.  What is listed below seemed to have worked.  However, it isn't even clear that having this as parameter is really required.  In fact as I experimented even further - this parameter isn't required at all as illustrated below.  It certainly can't be used to obtain more than one file from the post data.

     [HttpPost]  
     public ActionResult Edit(OfferModel offerModel)  
     {  
       try  
       {  
         if (ModelState.IsValid)  
         {  
           GetFiles(ref offerModel);  
   
           _rewardsAdminRepository.UpdateOffer(offerModel);  
   
           return RedirectToAction("Index", "Offer");  
         }  
   
         SetSelectList(offerModel);  
         DecodeHtml(ref offerModel);  
   
         return View(offerModel);  
       }  
       catch (Exception exception)  
       {  
         _log4Net.Error("Edit(POST)", exception);  
         return ProcessError(exception.Message);  
       }  
     }  

Then it occurred to me that the Request object is in scope during any method with the [HttpPost] attribute.  And that this Request object contains a property called Files that holds the names of each of the file input elements on the html form. Rather than clutter up the Edit method above and because a Create method would also need the ability to obtain the files from the Request object a private method called GetFiles was created.  Its implementation is listed below.

     private void GetFiles(ref OfferModel offerModel)  
     {  
       foreach (string fileName in Request.Files)  
       {  
         HttpPostedFileBase hpf = Request.Files[fileName];  
   
         if (hpf != null && hpf.ContentLength > 0)  
         {  
           if (fileName.Equals("thumbnailImage"))  
           {  
             DisplayImageModel imageModel = GetImageData(hpf);  
             offerModel.ThumbnailBytes = imageModel.ImageBytes;  
             offerModel.ThumbnailMimeType = imageModel.ImageMimeType;  
           }  
           else if (fileName.Equals("offerImage"))  
           {  
             DisplayImageModel imageModel = GetImageData(hpf);  
             offerModel.ImageBytes = imageModel.ImageBytes;  
             offerModel.ImageMimeType = imageModel.ImageMimeType;  
           }  
           else if (fileName.Equals("customerListFile"))  
           {  
             // we have a list of customer's, e.g. main bill numbers...  
             List<long> mainBillNumbers = GetCustomerList(hpf);  
   
             if (mainBillNumbers != null && mainBillNumbers.Count > 0)  
             {  
               foreach (long mainBillNumber in mainBillNumbers)  
               {  
                 offerModel.CustomerList.Add(mainBillNumber);  
               }  
             }  
           }  
         }  
       }  
     }  

As you can see the you can iterate through the Files collection and get a clear understanding of which element is sending a file in the post data.  As it happens the fileName represents the name provided to the input type element in the cshtml file.  Look at the declaration of the input elements below and compare it with the code above.

     <div class="editor-group">  
       <div class="editor-label">  
         @Html.LabelFor(m => m.ThumbnailId)  
       </div>  
       <div class="editor-field">  
         <input type="file" id="upload_ThumbImage" class="find_file_button" name="thumbnailImage" />  
         <p>  
           <img id="thumbnailImage" class="imagePreview" alt="No Image Selected" src="@Url.Action("GetImage", "DisplayImage", new {id = Model.ThumbnailId, imageType = "Thumbnail"})"/>    
         </p>  
       </div>  
     </div>  
   
     <div class="editor-group">  
       <div class="editor-label">  
         @Html.LabelFor(m => m.ImageId)  
       </div>  
       <div class="editor-field">  
         <input type="file" id="upload_OfferImage" class="find_file_button" name="offerImage" />  
         <p>  
           <img id="offerImage" class="imagePreview" alt="No Image Selected" src="@Url.Action("GetImage", "DisplayImage", new {id = Model.ImageId, imageType = "Image"})"/>   
         </p>  
       </div>  
     </div>  

The code that actually pulls the image file data and saves it is implemented in the GetImageData method illustrated below.  This method also does some checking to ensure that only certain images types are used.
     private DisplayImageModel GetImageData(HttpPostedFileBase imageFile)  
     {  
       if (imageFile.ContentType.ToLower().Equals("image/jpeg") ||  
         imageFile.ContentType.ToLower().Equals("image/png"))  
       {  
         using (MemoryStream ms = new MemoryStream())  
         {  
           imageFile.InputStream.CopyTo(ms);  
           DisplayImageModel imageModel = new DisplayImageModel  
           {  
             ImageMimeType = imageFile.ContentType.ToLower(),  
             ImageBytes = ms.ToArray()  
           };  
   
           return imageModel;  
         }  
       }  
       return null;  
     }