Web developer from Sydney Australia. Currently using asp.net, mvc where possible.

Saturday, April 17, 2010

ASP.NET Force the Browser to Really CACHE Content


Recently I have been optimizing the load time for our site Jobping. Getting the browser to really cache a file (not request that file for the duration specified) is something that always takes some time to get right.

So I just wanted to share the code to achieve it and hope it helps someone else out.

TimeSpan duration = TimeSpan.FromMinutes(cacheDurationInMinutes);
HttpCachePolicy cache = HttpContext.Current.Response.Cache;
cache.SetCacheability(HttpCacheability.Public);
cache.SetExpires(DateTime.Now.Add(duration));
cache.SetMaxAge(duration);
cache.AppendCacheExtension("must-revalidate, proxy-revalidate");

FieldInfo maxAgeField = cache.GetType().GetField("_maxAge", BindingFlags.Instance | BindingFlags.NonPublic);
maxAgeField.SetValue(cache, duration);

This will instruct the browser to store a copy of the url andfor the duration given. So be sure that this is applicable for your content before using this code (YMMV).




kick it on DotNetKicks.com

Sunday, June 15, 2008

Mvc.Net preview 3 validation - server and client side

WORKING EXAMPLE: http://www.xsation.net.au/Samples/MvcValidation/Comment.aspx
DOWNLOAD SOURCE: http://www.xsation.net.au/Samples/MvcValidation/MvcValidationSample.zip

If you need some basics on MVC check out:

Over the last few weeks I have been toying with .net .mvc framework.

While enjoying the freedom that mvc offers from the 'old school'(too early?) .net forms , there are a few sore points
when working with mvc:

  1. No composite controllers

  2. No built in support for validation on the server and client site

Scott Gu has hinted that we will see support for validation in the upcoming releases of the mvc framework, however I need something now.

The rest of the post describes the approach I am trialling at the moment using jquery

The controller

First on the controller I wanted my action method to simply look like the following:

  1. Is this a get request? yes -> return the form(view)..

  2. It's a post. Is the form validated? no -> return the form with errors

  3. It's a validate post. Save the entity then redirect somewhere

The code example deals with a simple scenario where we collect a comment and displaying the list of comments collection so far on the page.

In my example, the c# code for the controller looks like this


//Initialize the view data
CommentListData vd = new CommentListData();

//add the current list of comments to the view data
vd.Comments = _comments;

//1. Its is a gets , just return the list of comments and form
if (Request.HttpMethod == "GET")
return Index();

//Check if the form is valid...
if (!vd.Form.Validate(Request.Form))
{
//if its not, the form will have all the errors messages populated in the viewddata (vd).
return View("Index", vd);
}

//Form is valid, so we can add our new comment.
Comment comment = new Comment();

//Set all validated properties
vd.Form.UpdateEntity(comment);
_comments.Add(comment);

///Redirects back to the comment list of comments.
return RedirectToAction( "Index");

Although a little long, it achieves the 3 goals above of how a simple form post should be handled.

The definition of the CommentListData object is show below

   public class CommentListData
{
public CommentListData() { this.Form = new CommentForm(); }

public List Comments { get; set; }
public CommentForm Form { get; set; }
}

This is pretty self explanatory, we have a list of comments and a form. The form object deals with the collection of the 3 inputs from the
posted form, 1) A person's name 2) A person's email and 3) the persons comment.

The form object provides the following basic functions

  1. Holds the raw (string) data posted from the raw http form

  2. Performs validation to determine if the data is valid

  3. Transfers the validated data into the real typed entity

The actual implementation of the CommentForm is posted below.


public class CommentForm : Form
{
public FormField Name = new FormField("Comment_Name");
public FormField EmailAddress = new FormField("Comment_EmailAddress");
public FormField Comments = new FormField("Comment_Comments");

public RequiredFieldValidator NameRequired;
public RequiredFieldValidator EmailAddressRequired;
public RequiredFieldValidator CommentsRequired;
public RegexFieldValidator EmailAddressRegex;

public override void AddValidators(List validators)
{
this.NameRequired = new RequiredFieldValidator("Please enter your name", Name);
this.EmailAddressRequired = new RequiredFieldValidator("Please enter your email address", EmailAddress);
this.CommentsRequired = new RequiredFieldValidator("Please enter your comments", Comments);
this.EmailAddressRegex = new RegexFieldValidator("Please check the format of your email address", EmailAddress, "..*@..*\\...*");

validators.Add(this.NameRequired);
validators.Add(this.EmailAddressRequired);
validators.Add(this.CommentsRequired);
validators.Add(this.EmailAddressRegex);
}
}

Here we define our 3 fields that we wish to collection form the raw http form. Then we add all our validators to the form

The CommentForm inherits from a generic Form object, we pass our Comment object in as the generic. The base form object can handle the following functions now it has a Comment object

  1. It can validate the posted form using the validates we have described

  2. It can set the form up with initial values from a Comment object( used for editing purposes)

  3. It can updated the Comment entity based on a valid form

The Form object uses reflection and some exiting MVC helpers that also use reflection to achieve these functions.

The implementation details for the form object can be browsed in the download available at the top of this article.

The html view layer

The full html for the comment form is posted below. Its should be pretty straight forward for those familiar with .net and html.

For those keeping up to date with mvc you will notice a lack of the Html Helper methods, this is a personal preference. I like to have my html out in open for everyone to see, not hidden in some server method.


<fieldset class="comment">
<legend>Add your comment</legend>
<div class="inner">
<form id="addCommentForm" method="post" action="<%= Url.AddCommentUrl() %>">


<ul id="error_list" class="error" <%= Html.RenderDisplayNone(this.ViewData.Model.IsValid) %> >
<h6>There were errors!</h6>
<% foreach(var e in this.ViewData.Model.Validators){ %>
<li <%= Html.RenderDisplayNone(e.IsValid ?? true) %> class=" <%= e.ClientId %>" ><%= e.Message %></span></li>
<% } %>
</ul>
<div class="row">
<label for="Comment_Name">Name:</label>
<input id="Comment_Name" name="Comment_Name" class="text" value="<%= this.ViewData.Model.Name %>" type="text" />
<span class="error Comment_Name_Required" style="display: none" >« <%= this.ViewData.Model.NameRequired.Message %></span>
</div>

<div class="row">
<label for="Comment_EmailAddress">Email:</label>
<input id="Comment_EmailAddress" name="Comment_EmailAddress" value="<%= this.ViewData.Model.EmailAddress %>" type="text" />
<span class="error Comment_EmailAddress_Required" style="display: none" >« <%= this.ViewData.Model.EmailAddressRequired.Message%></span>
<span class="error Comment_EmailAddress_Regex" style="display: none" >« <%= this.ViewData.Model.EmailAddressRegex.Message%></span>
</div>

<div class="row">
<label for="Comment_Comments">Comments:</label>
<textarea id="Comment_Comments" name="Comment_Comments" rows="10" cols="60"
><%= this.ViewData.Model.Comments %></textarea><span class="error Comment_Comments_Required" style="display: none" >« !</span>
</div>

<div class="row button">
<input value="Add Comment" type="submit" />
</div>

<val:ClientValidators FormId="addCommentForm" Form="<%# this.ViewData.Model %>" runat="server" />
</form>
</div>
</fieldset>

There are a couple of things to note here, firstly the ClientValidators server control a the bottom. This can be viewed in the download
but basically it initializes a bunch of javascript objects to help with client side validation (output of the control shown below). The script attaches the validators to the actual html form DOM object


<script type="text/javascript">var form = document.getElementById('addCommentForm');
form.validators = new Validators();
form.validators.array[0] = new Validator('Please enter your name', 'validate_RequiredField', 'Comment_Name_Required', 'Comment_Name' , '');
form.validators.array[1] = new Validator('Please enter your email address', 'validate_RequiredField', 'Comment_EmailAddress_Required', 'Comment_EmailAddress' , '');
form.validators.array[2] = new Validator('Please enter your comments', 'validate_RequiredField', 'Comment_Comments_Required', 'Comment_Comments' , '');
form.validators.array[3] = new RegexValidator('Please check the format of your email address', 'Comment_EmailAddress_Regex', 'Comment_EmailAddress' , '', '..*@..*\...*');
</script>

Secondly, the html inputs are initialized from the form values, this allows an invalid value to be posted to the server and then redisplayed with an error message. (it allows us to keep invalid values during a round trip)

Thirdly, there is a simple Html helper, to render "style="display: none" based on a boolean value.

JQuery is used to perform the client side validation. It makes use of the array of validators to perform client side validation. A portion of the jquery is show below.


//when ready
$(document).ready(function(){

//get every form in this document (1 in this case)
$("form").each(
function()
{
//if validators exist
if(this.validators)
{
//attach the validates to the inputs
attachValidator(this.validators);

//attach efferts to the inputs
attachValidatorEffects(this.validators);
}

$(this).submit(function()
{
if(this.validators)
{
var isValid = this.validators.validate();
var errorList = $('#error_list');
errorList.hide();

for(var i=0; i < this.validators.array.length; i++)
{
UpdateValidatorDisplay(this.validators.array[i]);
}

if(!isValid)
errorList.fadeIn();
else
$(this).css("visibility","hidden");

return isValid;
}
});
});
});


function attachValidatorEffects(validators)
{
//loop through each validotor
for(var i=0; i < input =" $(validators.array[i].targetDom);" j="0;">

I'll be the first to admit my javascript skills are lacking but I did get the job done. The form in action can be viewed here
http://www.xsation.net.au/Samples/MvcValidation/Comment.aspx.

This is a rough start to validation using mvc and I am sure Scott and the team will come up with something far better. But for now, I hope it either helps you out a bit or provides some discussions points moving forward.

kick it on DotNetKicks.com

Monday, November 26, 2007

Forget Page Methods move on to Script Services

This entry is to guide you through the transition from the page method to the script service. Don't worry it won't hurt a bit...

Full Sample Code

Using page methods is a great idea for getting more performance out of your ajax apps but if they have a few draw backs

  1. They don’t promote reusability, user controls methods would but sadly they missed the boat.

  2. Getting them to actually work on a hosted server is somewhat of a mistory (see: http://www.west-wind.com/WebLog/posts/152493.aspx)

The best thing about page methods is that the converted me to using Web Services or Script Services. Script services are regular web services with a few extra attributes. The good thing is that you can have a javascript proxy built for you just like a page method. Better still, the web service is actually usable from anywhere...

The hello world of the script service.

Fire up visual studio and start a new Web Project (or web site if you must) .

Add a new Web Service

The Hello world template should be already present, as below .... (I changed the method name to ‘Hello’ so it’s different from the class name)

/// <summary>
/// Summary description for HelloWorld
/// </summary>
[WebService(Namespace = "http://tempuri.org/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
[ToolboxItem(false)]
public class HelloWorld : System.Web.Services.WebService
{

[WebMethod]
public string Hello()
{
return "Hello World";
}
}

The add a reference to the System.Web.Extensions library and add the script service attribute to the class as below.
using System;
using System.Web.Services;
using System.Web.Services.Protocols;
using System.ComponentModel;

using System.Web.Script.Services;

namespace ScriptServiceExample
{
/// <summary>
/// Summary description for HelloWorld
/// </summary>
[WebService(Namespace = "http://tempuri.org/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
[ToolboxItem(false)]
[ScriptService]
public class HelloWorld : System.Web.Services.WebService
{

[WebMethod]
public string Hello()
{
return "Hello World";
}
}
}
That’s it. Done. We have converted out web service into a script service. Now we will move onto the default.aspx file and consume our script service.

Firstly check you web.confg has the necessary handlers defined.

<httphandlers>
<remove verb="*" path="*.asmx">
<add verb="GET,HEAD" path="ScriptResource.axd" type="System.Web.Handlers.ScriptResourceHandler, System.Web.Extensions, Version=1.0.61025.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" validate="false">
<add verb="*" path="*.asmx" type="System.Web.Script.Services.ScriptHandlerFactory" validate="false">
</add>

</add></remove></httphandlers>
Now, Add a ScriptManger to the page and define the script service as follows.
<asp:scriptmanager id="ScriptManager1" runat="server">
<services>
<asp:servicereference path="/HelloWorld.asmx">
</asp:servicereference>
</services>

</asp:scriptmanager>
The web page will compile and run at this stage but viewing a blank page less than impressive.

If you got firebug or other means you can run the page and check that the following javascript is actually generated and linked to the page..

Type.registerNamespace('ScriptServiceExample');
ScriptServiceExample.HelloWorld=function() {
ScriptServiceExample.HelloWorld.initializeBase(this);
this._timeout = 0;
this._userContext = null;
this._succeeded = null;
this._failed = null;
}
ScriptServiceExample.HelloWorld.prototype={
Hello:function(succeededCallback, failedCallback, userContext) {
return this._invoke(ScriptServiceExample.HelloWorld.get_path(), 'Hello',false,{},succeededCallback,failedCallback,userContext); }}
ScriptServiceExample.HelloWorld.registerClass('ScriptServiceExample.HelloWorld',Sys.Net.WebServiceProxy);
ScriptServiceExample.HelloWorld._staticInstance = new ScriptServiceExample.HelloWorld();
ScriptServiceExample.HelloWorld.set_path = function(value) {
var e = Function._validateParams(arguments, [{name: 'path', type: String}]); if (e) throw e; ScriptServiceExample.HelloWorld._staticInstance._path = value; }
ScriptServiceExample.HelloWorld.get_path = function() { return ScriptServiceExample.HelloWorld._staticInstance._path; }
ScriptServiceExample.HelloWorld.set_timeout = function(value) { var e = Function._validateParams(arguments, [{name: 'timeout', type: Number}]); if (e) throw e; if (value < _timeout =" value;" get_timeout =" function()" set_defaultusercontext =" function(value)" _usercontext =" value;" get_defaultusercontext =" function()" set_defaultsucceededcallback =" function(value)" e =" Function._validateParams(arguments," _succeeded =" value;" get_defaultsucceededcallback =" function()" set_defaultfailedcallback =" function(value)" e =" Function._validateParams(arguments," _failed =" value;" get_defaultfailedcallback =" function()" hello=" function(onSuccess,onFailed,userContext)" href="http://www.blogger.com/post-edit.g?blogID=3340352991227979961&postID=790319424207465397">Ping the server
This will not work yet because the function is defined as “ScriptServiceExample.HelloWorld.Hello” but just to encapsulate this call I am going to add an helper javascript file called HelloWorldHelper.js

The final html looks like this..

<form id="form1" runat="server">


<div>
<asp:scriptmanager id="ScriptManager1" runat="server">
<services>
<asp:servicereference path="/HelloWorld.asmx">
</asp:servicereference>
</services>

<script src="helloWorldHelper.js" type="text/javascript"></script>
<a href="javascript: Hello()">Ping the server</a>

</asp:scriptmanager><div id="processing" style="display: none;">
fetching
</div>
<div id="results">
</div>

</div>
</form>
NB: You should move the script include into the head of your document

The javascript file looks like this..

function Hello()
{
$get('processing').style.display = 'block';
ScriptServiceExample.HelloWorld.Hello(SuccessCallBack);
}

function SuccessCallBack(result)
{
$get('results').innerHTML = result;
$get('processing').style.display = 'none';
}

That’s it, consuming your script service couldn’t be easier.

Sending complex results to your web page is as easy as returning an object from your web service. The object will automatically be serilised using JSON (although you can specify XML if you want).

Quick code for returning an object.

[WebService(Namespace = "http://tempuri.org/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
[ToolboxItem(false)]
[ScriptService]
public class HelloWorld : System.Web.Services.WebService
{

[WebMethod]
public HelloWorldResponse Hello()
{
HelloWorldResponse r = new HelloWorldResponse();
r.Message = "Hello World";
r.Time = DateTime.Now;
return r;
}
}

[Serializable]
public class HelloWorldResponse
{
public string Message;
public DateTime Time;
}

Comsuming the object, client side

function Hello()
{
$get('processing').style.display = 'block';
ScriptServiceExample.HelloWorld.Hello(SuccessCallBack);
}

function SuccessCallBack(result)
{
var resultObject = eval(result)
$get('results').innerHTML = "The Message is:" + resultObject.Message + " The message was created on " + resultObject.Time;
$get('processing').style.display = 'none';
}

Visit the offical site for more info

Tuesday, November 6, 2007

Xsation.Net: Emailing: An alternate file upload method

Xsation.Net: Emailing: An alternate file upload method

Emailing: An alternate file upload method

On a recent project there was a requirement for an easy upload method for files. We considered a few options such as drag and drop file upload components, ftp , file synchronization etc. However since the files arrived via email the simplest method (for the user) was to have the email and attachment forwarded onto a specified email address.

When an email is delivered to the address, the asp.net website picks up the email and saves any attachments into a POP email inbox folder on the server. Then emails are then sorted and the attachments are moved to the required directory based on the business rules of the system.

This is the first time I have used such an approached and has been a great success in terms of ease of use. The client is very familiar with forwarding emails and the process has become second nature.

There are a few draw backs to be aware of when using this process, mainly concerning the slow nature of the POP mail servers in use. The mail takes a about 3 to 5 minutes to arrive on the site in the correct location. This is having the web site check the emails every minute.

Technically we had to ensure that there is only 1 web client checking the email at once. In testing, we had many emails delivered, which causes the download of the email to take longer the 1 minute. After which another client started and because the email client would delete the email after downloading the contents this caused major problems. The solution was to stop the secondary clients launching if a client was already running.

Apart from the above the solution is up and running well. It's definitely something to keep in mind next time you have to deal with file uploads to a website