Tuesday, July 10, 2012

ASP.NET MVC Remote Validation

public class TimeCard : IValidatableObject
{
...
[Remote("CheckUserName", "Home", ErrorMessage="Username is invalid")]
public string UserName{get; set;}
...
}

public class HomeController : Controller
{
...
public JsonResult CheckUsername(string username)
{
var result = false;
if(username == "tkhuc")
{
result = true;
}
return Json(result, JsonRequestBehavior.AllowGet);
}

ASP.NET MVC Custom Client Validation

Implement IClientValidatable
Implement a jQuery validation method
Implement an unobtrusive adapter

public class GreaterThanDateAttribute : ValidationAttribute, IClientValidatable
{
...
public IEnumerable<ModelClientValidationRule> GetClientValidationRules(
ModelMetadata metadata, ControllerContext context)
{
var rule  = new ModelClientValidationRule();
rule.ErrorMessage = FormatErrorMessage(metadata.GetDisplayName());
rule.ValidationType = "greater";
rule.ValidationParameters.Add("other", otherPropertyName);
yield return rule;
}

customvalidation.js
/// <reference path="jquery-1.4.4-vsdoc.js" />
/// <reference path="jquery.validate-vsdoc.js" />
/// <reference path="jquery.validate.unobtrusive.js" />

jQuery.validator.addMethod("greater", function(value, element, param)
{
return Date.parse(value) > Date.parse($(param).val());
});

jQuery.validator.unobtrusive.adapters.add("greater", ["other"], function(options){
options.rules["greater"] = "#" + options.params.other;
options.messages["greater"] = options.message;                    // ~ rule.ErrorMessage
});

Generated html output
<input class="text-box-single-line" id="EndDate" name="EndDate" type="text" value="1/1/2010 12:00:00 AM"
data-val="true"
data-val-greater="EndDate must be greater than StartDate"
data-val-greater-other="StartDate"
/>

ASP.NET MVC Client Validation

<script src="jquery-1.4.4.min.js" type="text/javascript"></script>
<script src="jquery.validate.min.js" type="text/javascript"></script>
<script src="jquery.validate.unobtrusive.min.js" type="text/javascript"></script>

Global Settings
<appSettings>
<add key="ClientValidationEnabled" value="true"/>
<add key="UnobtrusiveJavascriptEnabled" value="true"/>
</appSettings>


Page-level Settings
@Html.EnableClientValidation(false/true)
@Html.EnableUnobtrusiveJavascript(false/true)


Unobtrusive Javascript Validation
<input id="ConfirmHours" name="ConfirmHours" type="text value="0" class="text-box-single-line"
data-val="true"
data-val-equalto="ConfirmHours and Hours do not match."
data-val-equalto-other="*.Hours"
data-val-number="The field ConfirmHours must be a number."
data-val-range="The field ConfirmHours must be between 1 and 120."
data-val-range-max="120"
data-val-range-min="1"
data-val-required="The ConfirmHours field is required."
/>
<span class="field-validation-valid" data-valmsg-for="ConfirmHours" data-valmsg-replace="true"/>

Data Validation using Data Annotations

Data Annotations

[Required()]
[StringLength(25)]

[Range(1,120)]
public int Hours{get; set;}

[Compare("Hours")]
public int ConfirmHours{get; set;}

<div class="editor-label">
@Html.LabelFor(model=>model.ConfirmHours, "Please confirm the hours worked")
</div>
<div class="editor-field">
@Html.EditorFor(model=>model.ConfirmHours)
@Html.ValidationMessageFor(model=>model)
</div>

Custom Validation Attributes

public class GreaterThanDateAttribute : ValidationAttribute
:base("{0} must be greater than {1}")
{
public string OtherPropertyName{get; set;}

public GreaterThanDateAttribute(string otherPropertyName)
{
OtherPropertyName = otherPropertyName;
}
public override string FormatErrorMessage(string name)
{
return String.Format(ErrorMessageString, name, otherPropertyName);
}

protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
var otherPropertyInfo = validationContext.ObjectType.GetProperty(otherPropertyName);
var otherDate = (DateTime)otherPropertyInfo.GetValue(validationContext.ObjectInstance, null);
var thisDate = (DateTime)value;
if(thisDate <= otherDate)               //failed
{
var message = FormatErrorMessage(validationContext.DisplayName);
return new ValidationResult(message);
}

return null;                                      //succeeded
}
}

public DateTime StartDate{get; set;}

[GreaterThanDate("StartDate")]
public DateTime EndDate{get; set;}


Sunday, July 8, 2012

MVC 3 Child Output Caching

Controllers\HomeController.cs
public class HomeController : Controller
{
[OutputCache(Duration = 60)]
public ActionResult Index()
{
var model = DateTime.Now;
return View(model);
}

[ChildActionOnly]
[OutputCache(Duration = 10)]
public PartialViewResult CurrentTime(){
var model = DateTime.Now;
return PartialView(model);
}
}

Views\Home\CurrentTime.cshtml
@model DateTime
<p>This is the child action result, current time is: @Model.ToLongTimeString()</p>

Views\Home\Index.cshtml
@model DateTime
@{
ViewBag.Title = "Index";
Layout = "~/Views/Shared/_Layout.cshtml";
}
<h2>Index</h2>
<div>This is the index view for Home, rendering at time: @Model.ToLongTimeString()</div>
<div>@Html.Action("CurrentTime")</div>

MVC Request Validation

Be careful when sending HTML to server.

[ValidateInput(false)] - turn off all verification

[AllowHtml] - granular verification for view model

MVC 3 Action Results

HttpNotFoundResult
public ActionResult Results()
{
return HttpNotFound();
}

HttpRedirectResult
public ActionResult Results()
{
return RedirectPermanent("http://google.com");
}

HttpStatusCodeResult
public AtionResult Results()
{
return HttpStatusCodeResult(415, "Media type not recognized");
}

Global Filters

Controllers\HomeController.cs
public class HomeController : Controller
{
public ActionResult Index()
{
throw new InvalidOperationException();
return View();
}

Views\Shared\Error.cshtml
<h2>Sorry, an error occurred while processing your request.</h2>

Global.asax.cs
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new HandleErrorAttribute());
}

protected void Application_Start()
{
RegisterGlobalFilters(GlobalFilters.Filters);
}

Web.config
<system.web>
<customErrors mode="On"/>
....

Action filters: [Authorize], [HandleError]

Custom Action Filters
public class LogAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext){}
public override void OnActionExecuted(ActionExecutedContext filterContext){}
public override void OnResultExecuting(ResultExecutingContext filterContext){}
public override void OnResultExecuted(ResultExecutedContext filterContext){}
}