What is the best way to return File or ErrorMessage from Asp.net-mvc controller action?
Asked Answered
H

7

10

I have the following JavaScript code and controller action in my ASP.NET-MVC project:

Javascript:

$("#exportPPT").live('click', function (e) {
    window.location.href = "/Initiative/GenerateFile" + GenerateParams();
});

C# Controller:

    public ActionResult GenerateFile(MyParams myParams)
    {
        var template = Server.MapPath(PPT_ROOT + "/template.pptx");
        IEnumerable<Order> orders = Model.GetOrders(myparams);
        var pptResults = GeneratePowerpointFile(orders);
        return File(pptResults.Content, "application/vnd.ms-powerpoint", pptResults.FileName);
    }

But under certain conditions, let's say when orders.Count() is 0 then instead of generating a file, I would rather have an error message back to the user saying that you have an error.

What is the best way to achieve this given the code above? I thought of changing it to an AJAX call but I wasn't sure how to download my Fie() and package that inside a JSON request (or if that was supported).

Hideandseek answered 3/1, 2014 at 2:24 Comment(0)
S
11

I would initiate a $.get request to another controller action that would check for the order count. Return that value, along with an error message if appropriate. Display the error message when it's needed, otherwise process your redirect to download your file. It's an extra call to the controller but it allows you to have full control and handle the possibility of an error without redirecting your user.

$("#exportPPT").live('click', function (e) {
  $.get( "/Initiative/CheckForOrders" + GenerateParams(), function( data ) {
    if (data.IsValid) {
      window.location.href = "/Initiative/GenerateFile" + GenerateParams();
    } else {
      alert(data.ErrorMessage); // or show a div containing error message
    }
  });
});

Controller Action:

public ActionResult CheckForOrders(MyParams myParams)
{
  IEnumerable<Order> orders = Model.GetOrders(myparams);
  if (orders.Any())
    return Json(new { IsValid=true }, JsonRequestBehavior.AllowGet);

  return Json(new { IsValid=false, ErrorMessage="No orders" }, JsonRequestBehavior.AllowGet);
}
Sheepdog answered 14/1, 2014 at 14:14 Comment(3)
I wouldn't recommend this practice, see: en.wikipedia.org/wiki/Time_of_check_to_time_of_use. You could still end up with the generation of a document causing an error due to 0 records.Woodprint
@gridzbi agreed, also performance wise it requires 2 requests rather than 1.Cassiodorus
What happens if try{} catch{} then you have the error in the export file process? How you can return the message more friendly along with keeping the current page?Isaiasisak
C
5

I'd return a status that represents that a resource doesn't exist and return null? Then you can handle it accordingly in you javascript without worrying about doing multiple ajax calls to check one is available or the security implications if somebody bypasses such checks.

For example...

Controller

public ActionResult GenerateFile(MyParams myParams)
{
    var template = Server.MapPath(PPT_ROOT + "/template.pptx");
    IEnumerable<Order> orders = Model.GetOrders(myparams);

    if(!orders.Any()){
        Response.StatusCode = (int)HttpStatusCode.NotFound
        Response.StatusDescription = HttpStatusCode.NotFound.ToString();
        Response.TrySkipIisCustomErrors = true;
        return EmptyResult;
    } 

    var pptResults = GeneratePowerpointFile(orders);
    return new File(pptResults.Content, "application/vnd.ms-powerpoint", pptResults.FileName);
}
Cassiodorus answered 20/1, 2014 at 14:33 Comment(0)
S
1
if (count==0) return View();
else return File(...)

can't this work?

Stalactite answered 3/1, 2014 at 2:43 Comment(1)
This seems partly right. OP seems to be searching for a more elegant and complex solution.Pangenesis
P
1

I would suggest to redirect the user if orders.Count() is 0 or some other error occurs. Something like this.

public ActionResult GenerateFile(MyParams myParams)
{
    var template = Server.MapPath(PPT_ROOT + "/template.pptx");
    IEnumerable<Order> orders = Model.GetOrders(myparams);
    if(orders.Count() == 0){
       return RedirectToAction("Orders","ordersError",new { ID = "Error message"});
    }else{
       var pptResults = GeneratePowerpointFile(orders);
       return File(pptResults.Content, "application/vnd.mspowerpoint",pptResults.FileName);
    }
}

So you make an informative ordersError view that displays your error message.

Proudman answered 3/1, 2014 at 15:0 Comment(0)
F
1

If you will have to handle even internal server errors, you will have to set up a custom filter, apply it to your controller method, which intercepts disk related errors or any, and handles gracefully, and return your view some meaningfull data message.

please refer to this post, on creating a asp.net mvc filter, and related javascript code.

ASP.NET MVC Ajax Error handling

Forepaw answered 17/1, 2014 at 12:57 Comment(0)
D
1

Maybe this post could be useful: Download File Using Javascript/jQuery

I believe the JQuery File Download plugin deserves some attention, it handles errors gracefully :) Have a look on its online demo, too...

Duaneduarchy answered 19/1, 2014 at 17:46 Comment(0)
T
1

The answer of @Anthony Shaw is fine, but there are 2 concerns:

  1. What happens if you use try{} catch{} then you have the error in the export file process?
  2. How can you return the message more friendly along with keeping the current page?

The answer is You should use Ajax to achieve it in two steps:

  1. You should call ajax to generate a file along with store that memory data into Session or TempData.

Ajax

$ajax({
    cache: false,
    url: '/Initiative/GenerateFile',
    data: GenerateParams(), 
    success: function (data){
         var response = JSON.parse(data);
         if(response.IsSuccesful) {
           window.location = '/Initiative/DownloadFile?fileGuid=' + response.FileGuid 
                           + '&filename=' + response.FileName;
         }else{
           alert(response.ErrorMessage);
         }
    }
});

Controller

    public ActionResult GenerateFile(MyParams myParams)
    {
        IEnumerable<Order> orders = Model.GetOrders(myparams);
        if (orders.Count() == 0)
        {
            return new JsonResult
            {
                Data = new { IsSuccesful = false, ErrorMessage = "No orders" }
            };
        }
        try
        {
            var pptResults = GeneratePowerpointFile(orders);
            var guid = Guid.NewGuid().ToString();
            TempData[guid] = pptResults.Content;
            return new JsonResult
            {
                Data = new { IsSuccesful = true, FileGuid = guid, FileName = pptResults.FileName }
            };
        }
        catch (Exception err)
        {
            return new JsonResult
            {
                Data = new { IsSuccesful = false, ErrorMessage = err }
            };
        }

    }
  1. Create a new DownloadFile action to read the TempData[guid] then return that file as well.

    [HttpGet]
    public virtual ActionResult DownloadFile(string fileGuid, string fileName)
    {
        if (TempData[fileGuid] != null)
        {
            var data = TempData[fileGuid] as byte[];
            return File(data, "application/vnd.ms-powerpoint", fileName);
        }
        else
        {
            return new EmptyResult();
        }
    }
    

The great post to read Download Excel file via AJAX MVC

Titi answered 8/1, 2021 at 7:49 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.