<%@ WebHandler Language="C#" Class="RichScriptsActionExecutor" %> // SAMPLE ONLY. Requires ActionDemoState.cs in App_Code and authenticated sessions. // This operates fictional session data, NEVER a real customer database. using System; using System.Collections.Generic; using System.IO; using System.Web; using System.Web.Script.Serialization; using System.Web.SessionState; public class RichScriptsActionExecutor : IHttpHandler, IRequiresSessionState { public bool IsReusable {get{return false;}} readonly JavaScriptSerializer json=new JavaScriptSerializer {MaxJsonLength=65536}; void Reply(HttpContext c,int code,object data){c.Response.StatusCode=code;c.Response.Write(json.Serialize(data));} public void ProcessRequest(HttpContext c) { c.Response.ContentType="application/json";c.Response.TrySkipIisCustomErrors=true;c.Response.SuppressFormsAuthenticationRedirect=true; c.Response.Cache.SetCacheability(HttpCacheability.NoCache);c.Response.Cache.SetNoStore(); if(c.User==null||!c.User.Identity.IsAuthenticated){Reply(c,401,new{error="Sign in required."});return;} string owner=c.User.Identity.Name; if((string)c.Session["rs_demo_owner"]!=owner){c.Session.Remove("rs_demo_state");c.Session["rs_demo_owner"]=owner;} var state=c.Session["rs_demo_state"] as RichScriptsActionDemoState; if(state==null){state=new RichScriptsActionDemoState();c.Session["rs_demo_state"]=state;} // Read current sample data. No inference calls. if(c.Request.HttpMethod=="GET"){Reply(c,200,state.Snapshot("Sample inbox ready."));return;} if(c.Request.HttpMethod!="POST"){c.Response.AppendHeader("Allow","GET, POST");Reply(c,405,new{error="POST required."});return;} Uri origin; if(!Uri.TryCreate(c.Request.Headers["Origin"],UriKind.Absolute,out origin)||origin.GetLeftPart(UriPartial.Authority)!=c.Request.Url.GetLeftPart(UriPartial.Authority)){Reply(c,403,new{error="Origin rejected."});return;} string csrf=c.Session["rs_action_csrf"] as string; if(string.IsNullOrEmpty(csrf)||c.Request.Headers["X-CSRF-Token"]!=csrf){Reply(c,403,new{error="Invalid CSRF token."});return;} if(c.Request.ContentLength<1||c.Request.ContentLength>1024||!c.Request.ContentType.StartsWith("application/json",StringComparison.OrdinalIgnoreCase)){Reply(c,400,new{error="Invalid request."});return;} try { string body;using(var reader=new StreamReader(c.Request.InputStream))body=reader.ReadToEnd(); var input=json.Deserialize>(body);object raw; if(input==null||!input.TryGetValue("proposalId",out raw)||!(raw is string)||((string)raw).Length!=44)throw new ArgumentException(); bool cancel=input.ContainsKey("operation"); if(input.Count!=(cancel?2:1)||(cancel&&(!(input["operation"] is string)||(string)input["operation"]!="cancel")))throw new ArgumentException(); string id=(string)raw;var proposals=c.Session["rs_action_proposals"] as RichScriptsActionProposalStore; var proposal=proposals==null?null:proposals.Get(id,owner); if(cancel){ if(state.WasCompleted(id,owner)){Reply(c,409,new{error="Already executed. Cancellation cannot undo this operation."});return;} if(proposal==null)throw new InvalidOperationException(); proposals.Remove(id,owner);Reply(c,200,new{message="Proposal cancelled. Nothing executed.",sampleData=true});return; } // Only the ID is supplied by the browser. Action and arguments come from the server. // IRequiresSessionState serializes requests for this session, including retries. var result=state.Execute(proposal,id,owner,DateTime.UtcNow); if(proposal!=null)proposals.Remove(id,owner); Reply(c,200,result); }catch(UnauthorizedAccessException){Reply(c,403,new{error="Proposal belongs to another user."});} catch(TimeoutException){Reply(c,410,new{error="Proposal expired. Prepare a new request."});} catch(ArgumentException){Reply(c,400,new{error="Invalid action or record."});} catch(InvalidOperationException){Reply(c,409,new{error="Proposal is no longer available."});} } }