Overriding ActionResult GetDownload from plug-in

This topic was automatically closed 365 days after the last reply. New replies are no longer allowed.
11 anni tempo fa
I'm new to MVC. I'm trying to override the "GetDownload" url/mothod using a plug-in. I've got a basic plug-in framework setup but I don't know how to override the ActionResult GetDownload that is being called in Nop.Web\Controllers\DownloadControllers.cs

I tried to create a new route but I get an error about duplicate routes because the plug-in's route gets created before the core route.

Is there an example plug-in or code that shows how how an ActionResult can get overridden?

Thanks!
11 anni tempo fa
MichaelApproved wrote:
I'm new to MVC. I'm trying to override the "GetDownload" url/mothod using a plug-in. I've got a basic plug-in framework setup but I don't know how to override the ActionResult GetDownload that is being called in Nop.Web\Controllers\DownloadControllers.cs

I tried to create a new route but I get an error about duplicate routes because the plug-in's route gets created before the core route.

Is there an example plug-in or code that shows how how an ActionResult can get overridden?

Thanks!


The complaint about duplicate route perhaps is due to the same route name between your route and the original route. Try providing a different route name and try.

Also, to override the default route, specify a Priority higher than 0 should do the trick. :D
11 anni tempo fa
wooncherk wrote:
The complaint about duplicate route perhaps is due to the same route name between your route and the original route. Try providing a different route name and try.

Also, to override the default route, specify a Priority higher than 0 should do the trick. :D


Thanks. Changing the route name is what allowed me to set new route for "download/getdownload/{opvid}/{agree}" I thought the error was that the URL was duplicate, not the NAME.

So is rerouting the GetDownload url request the best way to override the DownloadController.GetDownload function?
11 anni tempo fa
MichaelApproved wrote:
The complaint about duplicate route perhaps is due to the same route name between your route and the original route. Try providing a different route name and try.

Also, to override the default route, specify a Priority higher than 0 should do the trick. :D

Thanks. Changing the route name is what allowed me to set new route for "download/getdownload/{opvid}/{agree}" I thought the error was that the URL was duplicate, not the NAME.

So is rerouting the GetDownload url request the best way to override the DownloadController.GetDownload function?


It depends. If eventually the code that made the difference is in one of the services, you would rather override the service instead of the controller. :D
11 anni tempo fa
wooncherk wrote:
The complaint about duplicate route perhaps is due to the same route name between your route and the original route. Try providing a different route name and try.

Also, to override the default route, specify a Priority higher than 0 should do the trick. :D

Thanks. Changing the route name is what allowed me to set new route for "download/getdownload/{opvid}/{agree}" I thought the error was that the URL was duplicate, not the NAME.

So is rerouting the GetDownload url request the best way to override the DownloadController.GetDownload function?

It depends. If eventually the code that made the difference is in one of the services, you would rather override the service instead of the controller. :D


The code is going to handle a redirect URL that is supposed to go to an Amazon S3 file. In order to maintain control of the number of downloads, I'm going to sign the URL so it expires after a few seconds. This way the customer will have to use the nop URL to download the file and the nop download restrictions will apply.

So, ultimately, the change should just be in the GetDownload function from the nop.web.controllers.downloadcontroller class. Is there a code example to override that? The route should work fine for what I'm doing but I'm curious to see how "public ActionResult GetDownload(Guid opvId, bool agree = false)" can be overridden from a plugin.
11 anni tempo fa
MichaelApproved wrote:
The complaint about duplicate route perhaps is due to the same route name between your route and the original route. Try providing a different route name and try.

Also, to override the default route, specify a Priority higher than 0 should do the trick. :D

Thanks. Changing the route name is what allowed me to set new route for "download/getdownload/{opvid}/{agree}" I thought the error was that the URL was duplicate, not the NAME.

So is rerouting the GetDownload url request the best way to override the DownloadController.GetDownload function?

It depends. If eventually the code that made the difference is in one of the services, you would rather override the service instead of the controller. :D

The code is going to handle a redirect URL that is supposed to go to an Amazon S3 file. In order to maintain control of the number of downloads, I'm going to sign the URL so it expires after a few seconds. This way the customer will have to use the nop URL to download the file and the nop download restrictions will apply.

So, ultimately, the change should just be in the GetDownload function from the nop.web.controllers.downloadcontroller class. Is there a code example to override that? The route should work fine for what I'm doing but I'm curious to see how "public ActionResult GetDownload(Guid opvId, bool agree = false)" can be overridden from a plugin.


I have not tested this on code, but logically since you are able to override the route, you can then provide any implementation of Action to that particular route. Meaning to say you don't need to override the GetDownload action of DownloadController by mean of inheriting from DownloadController. You just need to point that particular route to a new Action of a new Controller (which eventually resides in your plugin), and do all the dirty tricks in your new Action. :D
11 anni tempo fa
wooncherk wrote:
I have not tested this on code, but logically since you are able to override the route, you can then provide any implementation of Action to that particular route. Meaning to say you don't need to override the GetDownload action of DownloadController by mean of inheriting from DownloadController. You just need to point that particular route to a new Action of a new Controller (which eventually resides in your plugin), and do all the dirty tricks in your new Action. :D


I understand that I can essentially do anything I'd like once I've taken over the route. I plan on implementing the plugin this way.

But I'm wondering how to override an action for a future project. There are situations that come up where overriding a route isn't feasible and I'm wondering if there's a way to override functions in general. So, if I were to not use a route, would there be another way to override the GetDownload function?

Thanks for your answers!
11 anni tempo fa
MichaelApproved wrote:
I have not tested this on code, but logically since you are able to override the route, you can then provide any implementation of Action to that particular route. Meaning to say you don't need to override the GetDownload action of DownloadController by mean of inheriting from DownloadController. You just need to point that particular route to a new Action of a new Controller (which eventually resides in your plugin), and do all the dirty tricks in your new Action. :D

I understand that I can essentially do anything I'd like once I've taken over the route. I plan on implementing the plugin this way.

But I'm wondering how to override an action for a future project. There are situations that come up where overriding a route isn't feasible and I'm wondering if there's a way to override functions in general. So, if I were to not use a route, would there be another way to override the GetDownload function?

Thanks for your answers!


Basically you cant because all the Actions are not virtual methods. You can, at best, override those helper methods. :D
11 anni tempo fa
wooncherk wrote:
Basically you cant because all the Actions are not virtual methods. You can, at best, override those helper methods. :D


I see. So I'd have to update the source to include "virtual" in the definition but that would defeat the point of a plug-in. It'd be great to have the ability to override all the core functions with plug-ins.

This is what I have for my controller so far. My next step is to setup the configuration so that it doesn't require hard coding the Amazon Key and Secret.

The myGetDownload is pretty much the same as the original GetDownload but it calls the signDownload function to sign any S3 urls.


using System;
using System.Web.Mvc;
using Nop.Core;
using Nop.Core.Domain.Customers;
using Nop.Services.Catalog;
using Nop.Services.Media;
using Nop.Services.Orders;
using Nop.Web.Controllers;
using Amazon.S3;
using Amazon.S3.Model;

namespace Nop.Plugin.AmazonAWS.S3Download.Controllers
{
    public class myDownloadController : BaseNopController
    {
        private readonly IDownloadService _downloadService;
        private readonly IProductService _productService;
        private readonly IOrderService _orderService;
        private readonly IWorkContext _workContext;

        private readonly CustomerSettings _customerSettings;

        public myDownloadController(IDownloadService downloadService, IProductService productService,
            IOrderService orderService, IWorkContext workContext, CustomerSettings customerSettings)
        {
            this._downloadService = downloadService;
            this._productService = productService;
            this._orderService = orderService;
            this._workContext = workContext;
            this._customerSettings = customerSettings;
        }


        public ActionResult myGetDownload(Guid opvId, bool agree = false)
        {
            var orderProductVariant = _orderService.GetOrderProductVariantByGuid(opvId);
            if (orderProductVariant == null)
                return RedirectToRoute("HomePage");

            var order = orderProductVariant.Order;
            var productVariant = orderProductVariant.ProductVariant;
            if (!_downloadService.IsDownloadAllowed(orderProductVariant))
                return Content("Downloads are not allowed");

            if (_customerSettings.DownloadableProductsValidateUser)
            {
                if (_workContext.CurrentCustomer == null)
                    return new HttpUnauthorizedResult();

                if (order.CustomerId != _workContext.CurrentCustomer.Id)
                    return Content("This is not your order");
            }

            var download = _downloadService.GetDownloadById(productVariant.DownloadId);
            if (download == null)
                return Content("Download is not available any more.");

            if (productVariant.HasUserAgreement)
            {
                if (!agree)
                    return RedirectToRoute("DownloadUserAgreement", new { opvid = opvId });
            }


            if (!productVariant.UnlimitedDownloads && orderProductVariant.DownloadCount >= productVariant.MaxNumberOfDownloads)
                return Content(string.Format("You have reached maximum number of downloads {0}", productVariant.MaxNumberOfDownloads));


            if (download.UseDownloadUrl)
            {
                //increase download
                orderProductVariant.DownloadCount++;
                _orderService.UpdateOrder(order);

                //return result
                return new RedirectResult(signDownload(download.DownloadUrl));
            }
            else
            {
                if (download.DownloadBinary == null)
                    return Content("Download data is not available any more.");

                //increase download
                orderProductVariant.DownloadCount++;
                _orderService.UpdateOrder(order);

                //return result
                string fileName = !String.IsNullOrWhiteSpace(download.Filename) ? download.Filename : productVariant.Id.ToString();
                string contentType = !String.IsNullOrWhiteSpace(download.ContentType) ? download.ContentType : "application/octet-stream";
                return new FileContentResult(download.DownloadBinary, contentType) { FileDownloadName = fileName + download.Extension };
            }
        }

        private string signDownload(string url)
        {
            string urlDomainMatch = ".s3.amazonaws.com";

            string awsS3Key;
            string awsS3Secret;

            System.Uri urlParts = new System.Uri(url);

            awsS3Key = @"Your Amazon S3 Key";
            awsS3Secret = @"Your Amazon S3 Secret";

            /* is this an Amazon S3 url? */
            if (urlParts.Host.ToLower().Contains(urlDomainMatch))
            {
                //We've got a match! This is an amazon s3 link that needs to be signed. Let's begin...

                //Parse the bucket name. It's the subdomain of the host.
                string urlBucket = urlParts.Host.Split('.')[0];

                //Parse the path of the url.
                //UrlDecoding because Amazon SDK doesn't want an encoded path when looking up the key.
                //Ignore the leading slash with the substring.
                string urlPath = Server.UrlDecode(urlParts.PathAndQuery.Substring(1));
                
                //Parse the filename from the url by looking for the last forward slash and getting everything after it.
                string urlFilename = urlPath.Substring(urlPath.LastIndexOf('/') + 1);

                //Do we have all the settings we need?
                if (!string.IsNullOrEmpty(awsS3Key) &&
                        !string.IsNullOrEmpty(awsS3Secret) &&
                        !string.IsNullOrEmpty(urlBucket) &&
                        !string.IsNullOrEmpty(urlFilename))
                {
                    //make sure the file actually exists on S3
                    AmazonS3 client = (AmazonS3Client)Amazon.AWSClientFactory.CreateAmazonS3Client(awsS3Key, awsS3Secret);

                    GetObjectRequest reqS3 = new GetObjectRequest().WithBucketName(urlBucket).WithKey(urlPath);
                    try
                    {
                        S3Response respS3 = client.GetObject(reqS3);
                        //above code will fail if the file is missing or there's another error and skip the code below this.

                        //Let's sign the url and redirect the customer to the file on Amazon.
                        //Give it an expiration of 60 seconds (playing it safe) to account for a potential difference in local server and amazon server's time.
                        //It'll also account for any latency in the redirect time.
                        //We're expiring the URL so the customer will have to return to the store's download url if they want another download.
                        //This allows the store to regulate the number of downloads allowed.
                        Amazon.S3.Model.GetPreSignedUrlRequest signedUrl;

                        signedUrl = new Amazon.S3.Model.GetPreSignedUrlRequest();
                        signedUrl.WithBucketName(urlBucket);
                        signedUrl.WithExpires(DateTime.UtcNow.AddSeconds(60));
                        signedUrl.WithKey(urlPath);

                        //Add the content-disposition header value as attachment so it opens a download/save as window.
                        ResponseHeaderOverrides respOverride = new ResponseHeaderOverrides();

                        respOverride.ContentDisposition = string.Format("attachment;filename=\"{0}\"", urlFilename);

                        signedUrl.WithResponseHeaderOverrides(respOverride);

                        return client.GetPreSignedURL(signedUrl);

                    }
                    catch (AmazonS3Exception amazonS3Exception)
                    {
                        //Possible place to account for the "NotFound" error but, for now, we're going to throw all errors.
                        throw amazonS3Exception;
                        //if (amazonS3Exception.StatusCode.ToString() == "NotFound")
                        //{
                        //    //No found logic here.
                        //}
                        //else
                        //{
                        //    throw amazonS3Exception;
                        //}
                    }

                }
            }

            //If we fell to this code, we didn't get a host match, the S3 values weren't set or there was a problem in parsing the url.
            return url;

        }
    }
}

This topic was automatically closed 365 days after the last reply. New replies are no longer allowed.