24 Dec 2010

ASP.NET MVC 2 Freelance Contractor in Leeds, Yorkshire

I'm a Freelance C# / ASP.NET web application developer based in Leeds, Yorkshire, UK. I've years of experience and have just finished a funky AJAX-heavy MVC contract with oodles of ORM fun and my usual attention to User Experience. Do you want to hire me? In January I'll be free to do another contract so please get in touch if you think I could help!


17 Dec 2010

Lenovo T410 2-finger scroll edge problem

I love my new Lenovo T410, but the 2-finger scrolling didn't seem to be working right. The area of the touchpad in which the scroll would be detected seemed too narrow. Turns out that "gesture filtering" was enabled and needed to be turned off.

Go to mouse properties / ultranav / touchpad / settings / Smart Check Settings / Gesture Filtering

15 Dec 2010

Castle ActiveRecord / NHibernate Update Event Interceptor to Log Dirty Property Changes

Man I love the Castle Project documentation. Or at least, I would if I was a robot with a brain the size of a planet and an unlimited amount of time on my hands. Sadly I'm a human and find the whole thing impenetrable.

All I wanted to do was to intercept the NHibernate entity update event so that I could log the changes made. With kudos to this dude and this dude, here's how I did it:

1. In my project create a new class:

using Castle;
using Castle.ActiveRecord;
using Castle.ActiveRecord.Attributes;
using NHibernate;
using NHibernate.Event;

Note it uses a struct called EntityPropertyUpdate and a collection called RuntimeManager.EntityPropertyUpdates to keep a log of the changes. You'll have to roll your own if you want to do what I'm doing here, otherwise just modify the code as you see fit.

[EventListener()]
public class AuditUpdateListener : IPostUpdateEventListener
{
private const string _noValueString = "EMPTY";

private static string getStringValueFromStateArray(object[] stateArray, int position)
{
var value = stateArray[position];

return value == null || value.ToString() == string.Empty
? _noValueString
: value.ToString();
}

public void OnPostUpdate(PostUpdateEvent evt)
{
if (!(evt.Entity is Log_G))
{
var dirtyFieldIndexes = evt.Persister.FindDirty(evt.State, evt.OldState, evt.Entity, evt.Session);
foreach (var dirtyFieldIndex in dirtyFieldIndexes)
{
var oldValue = getStringValueFromStateArray(evt.OldState, dirtyFieldIndex);
var newValue = getStringValueFromStateArray(evt.State, dirtyFieldIndex);

if (oldValue != newValue)
RuntimeManager.EntityPropertyUpdates.Add(new RuntimeManager.EntityPropertyUpdate { EntityName = evt.Entity.GetType().Name, PropertyName = evt.Persister.PropertyNames[dirtyFieldIndex], PropertyFrom = oldValue, PropertyTo = newValue });
}
}
}
}


The other key thing is the ActiveRecordStarter.Initialize() call, which had to be changed to include the assembly containing the AuditUpdateListener class, which happened to be the main executing assembly. I was doing my AR Initialisation in global.asax so it now looks like this:


protected void Application_Start()
{
// ActiveRecord Setup
IConfigurationSource source = ActiveRecordSectionHandler.Instance;
Assembly asm = Assembly.Load("MyApp.DataLayer");
ActiveRecordStarter.Initialize(new Assembly[] {asm, Assembly.GetExecutingAssembly()}, source, new Type[] { });
}


That's all!

Incidentally though, if you want to use an ambient AR SessionScope like I do, add this to global.asax:

protected void Application_BeginRequest(Object sender, EventArgs e)
{
// Create a SessionScope for ActiveRecord
HttpContext.Current.Items.Add("ar.sessionscope", new SessionScope(FlushAction.Never));
}

protected void Application_EndRequest(object sender, EventArgs e)
{
// Close the SessionScope for ActiveRecord
try
{
SessionScope scope = HttpContext.Current.Items["ar.sessionscope"] as SessionScope;
if (scope != null)
scope.Dispose();
}
catch (Exception ex)
{
HttpContext.Current.Trace.Warn("Error", "EndRequest: " + ex.Message, ex);
}
}

6 Dec 2010

MVC File Upload - Using Uploadify with ASP.NET MVC 2 / 3


NOTE 23 Oct 2012

I no longer recommend the use of Uploadify with MVC. I've found benefit using javascript-only solutions such as DropZone : http://www.dropzonejs.com/

or Valum's File Uploader : https://github.com/Valums-File-Uploader/file-uploader




Uploadify is ace for performing file upload and image upload, but ASP.NET MVC docs were lacking, so I hope this helps someone. Critical points addressed here include:
  • Uploadify flash upload button is ugly so we use CSS to make it invisible and fake an HTML button under it.

  • The Uploadify flash upload doesn't pass the ASP.NET session and authentication cookies naturally, which we want for recognition and authorization, so we handle that.

UPLOADIFY FILES

Put all Uploadify files in a project folder called ClientScript/uploadify

SCRIPT

Put this code on the regular View page that performs the upload.

 <script type="text/javascript" src="<%= Url.Content("~/ClientScript/jquery-1.4.2.min.js")  %>"></script>
 <script type="text/javascript" src="<%= Url.Content("~/ClientScript/uploadify/swfobject.js") %>"></script>
 <script type="text/javascript" src="<%= Url.Content("~/ClientScript/uploadify/jquery.uploadify.v2.1.4.min.js")  %>"></script>
 
 <script type="text/javascript">

    $(function () {

        // Uploadify File Upload System
        // SessionSync data is sent in scriptData for security reasons, see UploadifySessionSync() in global.asax

        var UploadifyAuthCookie = '<% = Request.Cookies[FormsAuthentication.FormsCookieName] == null ? string.Empty : Request.Cookies[FormsAuthentication.FormsCookieName].Value %>'; 
        var UploadifySessionId = '<%= Session.SessionID %>';

        $("#fuFileUploader").uploadify({

            'hideButton'   : true,       // We use a trick below to overlay a fake html upload button with this hidden flash button                         
            'wmode'      : 'transparent', 
            'uploader': '<%= Url.Content("~/ClientScript/uploadify/uploadify.swf") %>',
            'cancelImg': '<%= Url.Content("~/ClientScript/uploadify/cancel.png") %>',
            'buttonText': 'Upload File',
            'script': '<%= Url.Action("FileUpload", "Media") %>',
            'multi': true,
            'auto': true,
            'scriptData' : { RequireUploadifySessionSync: true, SecurityToken: UploadifyAuthCookie, SessionId: UploadifySessionId },
            'onComplete' : function (event, ID, fileObj, response, data) 
                            {
                                response = $.parseJSON(response);
                                if (response.Status == 'OK')
                                {
         // Put your own handler code here instead of the following...
                                    alert('File Uploaded OK!');
                                }
                            }
        }); 
    });

 </script>

HTML

Put this HTML on the same View as the script above.

    <div style="position: relative;">
        
        <input type="button" id="btnUpload" value="Upload File" />

        <div style="position: absolute; top: 4px; left: 3px;">
            <input id="fuFileUploader" name="file_upload" type="file" />
        </div>
    </div>

GLOBAL.ASAX

Add the following bits.

 protected void Application_BeginRequest(Object sender, EventArgs e) 
 {
  if (HttpContext.Current.Request["RequireUploadifySessionSync"] != null)
   UploadifySessionSync();
 }

 /// <summary>
 /// Uploadify uses a Flash object to upload files. This method retrieves and hydrates Auth and Session objects when the Uploadify Flash is calling.
 /// </summary>
 /// <remarks>
 ///     Kudos: http://geekswithblogs.net/apopovsky/archive/2009/05/06/working-around-flash-cookie-bug-in-asp.net-mvc.aspx
 ///     More kudos: http://stackoverflow.com/questions/1729179/uploadify-session-and-authentication-with-asp-net-mvc
 /// </remarks>
 protected void UploadifySessionSync()
 {
  try
  {
   string session_param_name = "SessionId";
   string session_cookie_name = "ASP.NET_SessionId";

   if (HttpContext.Current.Request[session_param_name] != null)
    UploadifyUpdateCookie(session_cookie_name, HttpContext.Current.Request.Form[session_param_name]);
  }
  catch {}

  try
  {
   string auth_param_name = "SecurityToken";
   string auth_cookie_name = FormsAuthentication.FormsCookieName;

   if (HttpContext.Current.Request[auth_param_name] != null)
    UploadifyUpdateCookie(auth_cookie_name, HttpContext.Current.Request.Form[auth_param_name]);
  }
  catch {}
 }

 private void UploadifyUpdateCookie(string cookie_name, string cookie_value)
 {
  HttpCookie cookie = HttpContext.Current.Request.Cookies.Get(cookie_name);
  if (cookie == null)
   cookie = new HttpCookie(cookie_name);
  cookie.Value = cookie_value;
  HttpContext.Current.Request.Cookies.Set(cookie);
 } 

Models/MediaAssetUploadModel.cs
    public class MediaAssetUploadModel
    {
        public HttpPostedFileBase fileData { get; set; }
        public string SecurityToken { get; set; }
        public string Filename { get; set; }
    }

Controllers/MediaController.cs
 [Authorize]
 public ActionResult FileUpload(MediaAssetUploadModel uploadedFileMeta)
 {
  string fullFilePath = SaveUploadedFile(uploadedFileMeta);
  //TODO: Error handling
  return Json(new { Status = "OK" });
 }  

 //TODO: move this into a manager/repository class
 private string SaveUploadedFile(MediaAssetUploadModel uploadedFileMeta)
 {
  string fileName = Guid.NewGuid() + System.IO.Path.GetExtension(uploadedFileMeta.Filename);
  string fullSavePath = Path.Combine(ConfigurationManager.AppSettings["MediaAssetFolder"], fileName);
  uploadedFileMeta.fileData.SaveAs(fullSavePath);

  return fullSavePath;
 } 

Web.config

Make following changes to upload files to c:\temp , with a max file size of 100Mb.
Change these to suit yourself - c:\temp will get automatically cleared so you have been warned.
If you create your own upload folder, remember you will need to give write perms to the ASP.NET process.
 <configuration>
   <appSettings>
  <add key="MediaAssetFolder" value="c:\temp"/>
   </appSettings> 
   <system.web>
  <httpRuntime maxRequestLength="100000" /> 
   </system.web>
 </configuration>
If I helped you out today, you can buy me a beer below. Cheers!