Showing posts with label asp.net ajax. Show all posts
Showing posts with label asp.net ajax. Show all posts

Wednesday, August 15, 2012

MVC 3 + Elmah - Handle Ajax/json action errors in a generic way

Elmah is cool! Go grab it and learn if you don’t use it yet. Yet, the moment comes when you have plenty of mvc actions returning json and you just want to integrate together that Elmah and MVC error handling to provide some unified JSON error handling in case of handled and unhandled exceptions.

Lets start with a handling code that we might put into every action with the following catch block:

catch (Exception ex)
{
    var ticketId = Guid.NewGuid(); // Lets issue a ticket to show the user and have in the log
 
    Request.ServerVariables["TTicketID"] = ticketId.ToString(); // Elmah will show this in a nice table
 
    ErrorSignal.FromCurrentContext().Raise(ex); //ELMAH Signaling
 
    ex.Data.Add("TTicketID", ticketId.ToString()); // Trying to see where this one gets in Elmah
 
    return Json(new { Error = String.Format("Support ticket: {0}\r\n Error: {1}", ticketId, ex.ToString()) }, JsonRequestBehavior.AllowGet);
}

So we want to create some trouble ticket id to present to the user, log the error, and return json with the Error field (my convention, not important here). With all that we have javascript in browser being able to cope with the error plus everything is logged.

But this “per method” solution doesn’t scale well at all, should you have plenty of those methods. A helper class method to the rescue? Possible. But ASP.NET MVC is so cool! Its stack is ready to solve the task!

So lets delete the above catch and introduce the catch it all error filter by extending the standard HandleErrorAttribute:

using System.Web.Mvc;
using Elmah;
using OneNetConfigurator.Controllers;
 
namespace OneNetConfigurator.Helpers
{
    public class OncHandleErrorAttribute : HandleErrorAttribute
    {
        public override void OnException(ExceptionContext context)
        {
            // Elmah-Log only handled exceptions
            if (context.ExceptionHandled)
                ErrorSignal.FromCurrentContext().Raise(context.Exception);
 
            if (context.HttpContext.Request.IsAjaxRequest())
            {
                // if request was an Ajax request, respond with json with Error field
                var jsonResult = new ErrorController { ControllerContext = context }.GetJsonError(context.Exception);
                jsonResult.ExecuteResult(context);
                context.ExceptionHandled = true;
            }
            else
            {
                // if not an ajax request, continue with logic implemented by MVC -> html error page
                base.OnException(context);
            }
        }
    }
}

Generally, very simple. We check if request is an Ajax request and if it is, we respond with json data generated via the ErrorController (note how context is being passed to the controller) crafted in the following way:

public class ErrorController : Controller
{
    public ActionResult GetJsonError(Exception ex)
    {
        var ticketId = Guid.NewGuid(); // Lets issue a ticket to show the user and have in the log
 
        Request.ServerVariables["TTicketID"] = ticketId.ToString(); // Elmah will show this in a nice table
 
        
 
        ex.Data.Add("TTicketID", ticketId.ToString()); // Trying to see where this one gets in Elmah
 
        return Json(new { Error = String.Format("Support ticket: {0}\r\n Error: {1}", ticketId, ex.ToString()) }, JsonRequestBehavior.AllowGet);
    }

See?! This is our catch block that we don’t need to copy between actions anymore. Of course, those using Ajax in MVC in order to bring html snippets back to browser would have to use/add an extra option and ExecuteResult on some error view instead of JsonResult. This answer on stackoverflow describes the case.

You may consider as well adding Response.StatusCode = 500; just before returning that Json. More on that in the next post on this subject.

With classes above we configure filters in the Global.asax.cs (note that generic HandleErrorAttribute is commented out now, as we have extended it and use our error filter now):

public class MvcApplication : System.Web.HttpApplication
{
    public static void RegisterGlobalFilters(GlobalFilterCollection filters)
    {
        filters.Add(new GlobalAuthorise());
        filters.Add(new OncHandleErrorAttribute());
        //filters.Add(new HandleErrorAttribute());
    }

Now, we have json with the Error field returned. And we don’t have to do anything in the action itself!:

image

With possible handling in browser like this:

image

One more thing is to test our filter for “non-json” actions. And it works!

image

This is it, hope it might help someone.

More from me on json in mvc: JSON data islands in ASP.NET MVC

Friday, February 24, 2012

Json Data Islands in ASP.NET MVC

Once you have your island setup as always like:
<div id="dataIsland_in" style="display: none">
    @{
      Html.RenderAction("GetModelIsland", new { variantId = Model.BidVariant.Id });
    }
</div>
You might be surprised with your browser presenting you with “raw html” instead of that rendered page.
This is because with no precaution returning JsonResult would have an implication of content type to be set to “application/json”. To prevent this, just reset the content type to the one you need:
var result = Json(model, JsonRequestBehavior.AllowGet);
 
result.ContentType = "text/html";
 
return result;

Wednesday, February 3, 2010

Project Next - Client/browser side technology. Why not Silverlight.

Doing a lot of reading recently trying to understand the directions where the MS client/browser side development is going. I have to deliver a very intensive data-entry application in the next 7 months and that makes me wonder what is the best approach nowadays.

As go-live would be in a year from now I’m able to consider any betas. The options I started with were Silverlight 4.0 versus combination of ASP.NET MVC 2.0 with MvcContrib, jQuery, new Ajax features and probably some Silverlight with ASP.NET MVC host.

Silverlight 4.0 (Beta 1 now)sl4bloglogo

Prism v2 with MVVM would be the guiding frameworks to follow. Although as I understood Prism v2 is more about modular design and not exactly about achieving good MVVM (http://stackoverflow.com/questions/1595409/mef-vs-prism-what-is-the-difference-what-will-be-supported-in-the-future).

For the MVVM guidance/frameworks on Silverlight actually little can be found. Looks like there is a good one for WPF (with MEF, not Unity): http://waf.codeplex.com/ but still there is no real Silverlight support except for “experimental” release http://waf.codeplex.com/Release/ProjectReleases.aspx?ReleaseId=30057.

And if I understand it right MEF is going to be part of Silverlight 4.0 which in turn will support probably more of MVVM.

Evaluation:

+ Versatility. Silverlight can serve all-in-one purposes from data-entry to ink notes taking and video comments recording

+  Guidance. There is MVVM guidance with MEF

+  Duplex communication. This is very good compared to all other hack Ajax solutions like comet variations

? Reuse. They say you can reuse to some extent Silverlight views in WPF. Never witnessed such a reuse though, and not really relevant in my case.

+-  Scaffolding. There is a data access scaffolding story. Not exactly for Oracle though which is going to be my data storage. I’d have to buy an Oracle EF provider, not expensive but still involves then non-open-source 3party involved with all the support/upgrade etc implications.

+-  Cross Browser. Rather than reusing views in WPF I’ll need to expose some views to users with mobile browsers (like telco mobile dealers or self-care users) and though Silverlight 4.0 is even going to target chrome (http://timheuer.com/blog/archive/2009/11/18/whats-new-in-silverlight-4-complete-guide-new-features.aspx) this is still not enough.

+-  Testing. There is some unit testing story (http://code.msdn.microsoft.com/silverlightut) for Silverlight (and MVVM is a TDD enabler), but this supports at the moment SL up to v3. And this is not something of WatiN value.

- Community and frameworks support. Prism v2 only supports Silverlight v3 right now and somehow I don’t want to wait for Prism v4 mid summer (http://compositewpf.codeplex.com/Thread/View.aspx?ThreadId=80980). Overall, it looks like no one can keep up to speed with pace of Silverlight progress, it looks like a good idea to wait until v8 :) to avoid being on the “bleeding” edge.

 -  Speed. 

      Go to the Telerik  reports demo site and imagine yourself presenting that as part of your application. Think about how do you fill the gap for every control loading 30 seconds.  They first show you % indicator, then report loading progress indicator, then page loading progress indicator, this is just for user perception that something is happening during those 30 seconds!

     image => image => image

      Now open some pdf attachment in gmail and that is something you will not be ashamed of presenting as part of your application.

Conclusion:

Too many cons with real showstoppers for me. If I had to create an eye candy with no regard to practicality I’d try Silverlight. But this is clearly not my target.

I’m going forward with ASP.NET MVC and ajax approach with probably embedding Silverlight on the edges where required.

Monday, April 28, 2008

AjaxControlToolkit & ScriptResource.axd load balancer (web farm) related issue

It turns out that approaches that create resource url based onto methods that are in turn based on the AppRoot IIS metabase value can be actually very error prone.

One of such is the AjaxControlToolkit currently. Lets see how its ToolkitScriptManager creates script references:

protected override void OnResolveScriptReference(ScriptReferenceEventArgs e)
{

....
if (this._combinedScriptUrl == null)
{
this._combinedScriptUrl = string.Format(CultureInfo.InvariantCulture, "{0}?{1}={2}&{3}={4}", new object[] { (null != this._combineScriptsHandlerUrl) ? this._combineScriptsHandlerUrl.ToString() : this.Page.Request.Path, "_TSM_HiddenField_", this.HiddenFieldName, "_TSM_CombinedScripts_", HttpUtility.UrlEncode(SerializeScriptEntries(this._scriptEntries, false)) });
}

It can be an idea to do some normalization of path, something like ToUpper() or ToLower would do. Can though imagine REST path based case sensitive scenarios ... But the stable part of a path should/may be definitely normalized anyway.

What is the issue that happens? .

During partial page updates (when you use update panels), script loads may be requested by the partial updates that are coming back from the server. For example by the elements inside the |id|ScriptPath| .....

ScriptManager is doing a smart job by trying to see if any of the scripts referred by this partial update response is not actually present in its previously loaded list of scripts:

Sys._ScriptLoader.isScriptLoaded = function Sys$_ScriptLoader$isScriptLoaded(scriptSrc) {
// For Firefox we need to resolve the script src attribute
// since the script elements already in the DOM are always
// resolved. To do this we create a dummy element to see
// what it would resolve to.
var dummyScript = document.createElement('script');
dummyScript.src = scriptSrc;
return Array.contains(Sys._ScriptLoader._getLoadedScripts(), dummyScript.src);

Check happens based on the url of the script resource. Now imagine script resource from one server to be "/YOURVIRTUALDIRNAME/ScriptResource.axd?bla" and from another one: "/YourVirtualDirName/ScriptResource.axd?bla" .

In this case ScriptManager downloads the script, and again doing a smart thing, validates if any of the types defined by it are not already present on the page.

This is how in case of AjaxControlToolkit it first hits the:

Microsoft JScript runtime error: Sys.InvalidOperationException: Type AjaxControlToolkit.BoxSide has already been registered. The type may be defined multiple times or the script file that defines it may have already been loaded. A possible cause is a change of settings during a partial update.

And then in the post script load handler:


Microsoft JScript runtime error: Sys.ScriptLoadFailedException: The script '/YOURVIRTUALDIRNAME/ScriptResource.axd?d=TmjKPE4RZ97qx5GW9RVSkqSX0rxZscPBsKSyhveagjALaOxgNSakbVbBWc9HKZhJiVrfl3LartocAklCD5-ciQ2&t=633425518675405868' failed to load. Check for:
Inaccessible path.

As again the idea of just using ToUpper() or ToLower() would not work for all of the cases (like REST), the best way out looks to be just to ensure that AppRoot of those virtual dirs is exactly the same on all servers in the farm.

You can use Metabase Editor utility coming with the IIS Resource kit for the convenient editing of this. As you update it please pay attention to the whole part starting Root to be the same on all servers and better be Root and not ROOT. I don't have enough time to play with it now, but it looks like some magic converts the virtual dir app root name to capital if ROOT is used! This happens as it seems when IIS is flushing in-memory metabase snapshot to the disk. To be confirmed and be more funded hopefully soon.

Your problems with scripts can be though absolutely unrelated to this as there is quite a broad range of other issues you can have:
Non-matching machineKey on your farm, faulty script compression on IIS6, and range of others - happy googling :)!

Sunday, March 9, 2008

Json serialization of a DataTable - columns definition

Based on the existing example: http://www.dennydotnet.com/post/2007/09/A-DataTable-Serializer-for-ASPNET-AJAX.aspx and the explanation of a DataTable's json format:
http://weblogs.asp.net/infinitiesloop/archive/2006/12/14/ajax-futures-december-ctp-returning-datasets-datatables-and-datarows-from-a-webservice-or-pagemethod.aspx

I only added few lines of code to make it generate the simplest columns definition (names only) in the serialized structure to be friendlier with data-binding related methods like set_textProperty, etc in ASP.NET Futures scripts:

public override IDictionary<string, object> Serialize(object obj, JavaScriptSerializer serializer)
        {
            DataTable dt = obj as DataTable;
            Dictionary<string, object> result = new Dictionary<string, object>();

            if (dt != null && dt.Rows.Count > 0)
            {
                // List for row values
                List<object> rowValues = new List<object>();
                List<object> colDefinitions = new List<object>();

                foreach (DataColumn dc in dt.Columns)
                {
                    Dictionary<string, object> colDefinition = new Dictionary<string, object>();
                    colDefinition.Add("name", dc.ColumnName); // col name
                    colDefinitions.Add(colDefinition);
                }
                result["columns"] = colDefinitions;
                

                foreach (DataRow dr in dt.Rows)
                {
                    // Dictionary for col name / col value
                    Dictionary<string, object> colValues = new Dictionary<string, object>();

                    foreach (DataColumn dc in dt.Columns)
                    {
                        colValues.Add(dc.ColumnName, // col name
                         (string.Empty == dr[dc].ToString()) ? null : dr[dc]); // col value
                    }

                    // Add values to row
                    rowValues.Add(colValues);
                    //result.Add("row", colValues);
                }

                //// Add rows to serialized object
                result["rows"] = rowValues;
            }

            return result;
        }

Nasty Sys.Preview.Data.DataTable bug in the PreviewScript.js

This is based on the December CTP scripts I believe:

Original code:

Sys.Preview.Data.DataTable = function Sys$Preview$Data$DataTable(columns, tableArray) {
            this._array = Array.isInstanceOfType(tableArray) ? tableArray : [];
    this._columns = Array.isInstanceOfType(columns) ? columns : [];
    this._rows = [];

Which is not setting actually the _rows, which leads to numerous problems during data binding ASP.NET Futures databinding. Correction would be:

Sys.Preview.Data.DataTable = function Sys$Preview$Data$DataTable(columns, tableArray) {
            this._array = Array.isInstanceOfType(tableArray) ? tableArray : [];
    this._columns = Array.isInstanceOfType(columns) ? columns : [];
    this._rows = Array.isInstanceOfType(tableArray) ? tableArray : [];

Wednesday, February 27, 2008

WebHttpBinding (with enableWebScript) and Http 400 - Page not found

Was googling but didn't find the pointer to this exactly solution to the problem happened to me.
Having two virtual dirs with absolutely same setup and config, one was returning Http 400 and not even going to Application_BeginRequest, another was working just alright.

Virtual dirs were created at the different time. One I believe before the .NET 3.5 install and one after.

Anyway, just deleting the virtual directory and recreating it solved my problem.

No time to analyse metabase diffs etc, evening is late and release is close :).

Tuesday, April 17, 2007

ASP.NET Ajax - ‘AJAXControlToolkit is undefined’ - cachePolicy.SetLastModified((DateTime)GetAssemblyInfo(assembly).Second)

Problem declares itself as ‘AJAXControlToolkit is undefined’ on the client side (with few other client side exceptions before that). Server side exception info though provide enough insight into the problem...

That is another tricky one you can found in the ScriptResourceHandler.cs in the current ASP.NET Ajax.
Setting of the LastModified to anything that is newer than now from the server view is illegal (you will get ArgumentOutOfRangeException).
Though, how the GetAssemblyInfo(assembly).Second works?



private static DateTime GetLastWriteTime(Assembly assembly) {
string codeBase = GetCodeBaseWithAssert(assembly);
Uri codeBaseUri = new Uri(codeBase);
if (!codeBaseUri.IsFile) return DateTime.MinValue;
string localPath = codeBaseUri.LocalPath;

FileIOPermission p = new FileIOPermission(FileIOPermissionAccess.Read, localPath);
p.Assert();
return File.GetLastWriteTime(localPath);
}




The above takes the assembly the script located into and looks for it LastWriteTime.
Beware if you have that assembly copied from somewhere where this date may go ahead of the server time! That happens to us all the time when copying from Europe to USA :)!

P.S. Exception text on the server is like:

Event Type: Warning
Event Source: ASP.NET 2.0.50727.0
Event Category: Web Event
...
User: N/A
Computer: ... Description: Event code: ...
Event message: An unhandled exception has occurred.
....
Event sequence: 9 Event occurrence: 1 Event detail code: 0

Application information:
....

Process information: Process ID: ... Process name: ... Account name: ...

Exception information:
Exception type: ArgumentOutOfRangeException
Exception message: Specified argument was out of the range of valid values.
Parameter name: utcDate

Request information: Request URL: .../ScriptResource.axd?....
....

Stack trace: at System.Web.HttpCachePolicy.UtcSetLastModified(DateTime utcDate)
at System.Web.HttpCachePolicy.SetLastModified(DateTime date)
at System.Web.Handlers.ScriptResourceHandler.PrepareResponseCache(HttpResponse response, Assembly assembly)
at System.Web.Handlers.ScriptResourceHandler.ProcessRequest(HttpContext context)
at System.Web.Handlers.ScriptResourceHandler.System.Web.IHttpHandler.ProcessRequest(HttpContext context)
at System.Web.HttpApplication.CallHandlerExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute()
at System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously)

What would be the solution? - Touch the file your script resource is inside. There are few programs to do it, or you can just use simple .net console application for that:

class Program
    {
        static void Main(string[] args)
        {
            try
            {
                if (args.Length < 1)
                {
                    Console.WriteLine(String.Format(CultureInfo.InvariantCulture,
                        "Usage: SetFileModifiedDate \"FilePath\""));
                    return;

                }
                string filePath = args[0];

                if (!File.Exists(filePath))
                {
                    Console.WriteLine("File: \"" + filePath + "\" doesn't exist!");
                    return;

                }
                FileAttributes attr = File.GetAttributes(filePath);

                if ((attr & FileAttributes.ReadOnly) > 0)
                {
                    Console.WriteLine("File: \"" + filePath + "\" is readonly!");
                    return;
                }
                FileInfo fi = new FileInfo(filePath);
                fi.LastWriteTime = DateTime.Now.AddDays(-1);

                Console.WriteLine("Modified date is now set to: " + fi.LastWriteTime.ToString("ddMMMyyTHH:mm:ss fff"));
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.ToString());
            }
        }
    }

The only principal lines you can shorten your solution to:

FileInfo fi = new FileInfo(filePath);
fi.LastWriteTime = DateTime.Now.AddDays(-1);

That should be only few lines of powershell script as well.