Thursday, January 14, 2016

No More Postbacks! From MVC to WebForms.

I really love MVC. A lot. However, my current job heavily depends on ASP.NET WebForms and a complete conversion to MVC is just not feasible.

I've developed some techniques while working in this environment that have helped me tremendously, and I haven't seen them used anywhere else.
So I will share this one with anyone who is interested.

In particular, I'm mimicking controller actions from MVC in WebForms.

It's perfect for client-side JavaScript development and I use it to essentially drive all of the Kendo UI stuff I've implemented at work.

So I'll make this short and sweet.

I need to pass JSON to the client, but how do I do that without setting "hidden fields" in the code-behind and other silly WebForms magic?

This post explains how I've achieved this in the most natural way possible.

This technique can be used in any ASP.NET WebForms project that needs to be dynamic without using "postbacks" or "UpdatePanels."

We will use Ajax to request the same page we're on asynchronously, but with a query string to specify which action we want to execute.

/Foo.aspx
function ajax(action, type, dataType, data, callback) {  
 $.ajax({
  url: '/Foo.aspx?action=' + action,
  type: type,
  dataType: dataType,
  data: data,
  success: function(data) {
   if (callback && typeof(callback) === typeof(Function)) {
    callback(data);
   }
  }
 });
}

function print(data) {
 console.log(data);
}

ajax('GET_SomeJson', 'get', 'json', {}, print);

Behind the scenes, we get the "action" query string from the Request object, and route it through a Route method.

/Foo.aspx.cs
protected void Page_Load(object sender, EventArgs e) {
 if (!this.IsPostBack) {
  var action = Request.QueryString["action"];

  if (!string.IsNullOrEmpty(action)) {
   Route(action);
  }
 }
}

protected void Route(string action) {
 var json = string.Empty;

 switch (action.ToLower()) {
  case "get_somejson":
   json = new JavaScriptSerializer.Serialize(new Bar());
   break;
 }

 if (!string.IsNullOrEmpty(json)) {
  Response.Write(json);
  Response.End();
 }
}

// example class to serialize as json
public class Bar {
 public string Message { get; set; } = "Hello, World!";
}

When I need to pass parameters to one of my actions, I pass them in an object via $.ajax(), and I receive them through Request.Params on the server-side.

Here's an example (I'm recycling the ajax() function I wrote earlier):

/Foo.aspx
ajax('POST_ActionWithParams', 'post', 'text', { someParameter: 'some value' }, print);

I tap into Request.Params to build a dictionary and modify my Route function so that it can accept parameters.

/Foo.aspx.cs
protected void Page_Load(object sender, EventArgs e) {
 if (!this.IsPostBack) {
  var action = Request.QueryString["action"];
  var parameters = new Dictionary<string, string>();

  foreach (var key in Request.Params.AllKeys) {
   parameters.Add(key, Request.Params[key]);
  }

  if (!string.IsNullOrEmpty(action)) {
   Route(action, parameters);
  }
 }
}

protected void Route(string action, Dictionary<string, string> parameters) {
 var json = string.Empty;

 switch (action.ToLower()) {
  case "get_somejson":
   json = new JavaScriptSerializer.Serialize(new Bar());
   break;

  case "post_actionwithparams":
   json = ActionWithParams(parameters);
   break;
 }

 if (!string.IsNullOrEmpty(json)) {
  Response.Write(json);
  Response.End();
 }
}

private string ActionWithParams(Dictionary<string, string> values) {
 var value = string.Empty;

 foreach (var key in values.Keys) {
  switch (key.ToLower()) {
   case "someparameter":
    value = values[key];
    break;
  }
 }

 return value;
}

Now all I'm missing are models and views. :P

I figured this was a pretty neat trick and that other people might find it useful.

For the record, I don't mind working in WebForms, but I will always prefer MVC.