Ehsan Ghanbari

Experience, DotNet, Solutions

Image browse plugin in CKEditor

I was recently integrating my blog with CKEditor Image Browser plugin and it was interesting to me, It was cool! in order to add Image Browser plugin to your CKEditor you should get the file from the mentioned link and put it in your plugins folder inside CKEditor. Then you should go to the config.js file of CKEditor and add the target plugin:

CKEDITOR.editorConfig = function( config ) {

// Define changes to default configuration here. For example:

// config.language = 'fr';

// config.uiColor = '#AADC6E';

    config.extraPlugins = 'imagebrowser';

    config.extraPlugins = 'prism';

    config.extraPlugins = 'codesnippet';

    config.extraPlugins = 'widget';

    config.extraPlugins = 'dialog';

};

Now before browsing, if you run your application, you would see the image properties with  Browse server button:

 

 

Now you should tell the plugin that where your images should be getting, imageBrowser_listUrl is the URL which should return a JSON file:

 <textarea id="my-textarea-id"></textarea>

 <script type="text/javascript">

    CKEDITOR.replace('js-my-textarea', {

        "extraPlugins": 'imagebrowser',

        "imageBrowser_listUrl": "/Administrator/File/BrowseImages"

    });

</script>

As I've written my blog with MVC4, I used HTML helper instead of <textarea> tag:

     @Html.TextAreaFor(blog => blog.Body, new { @id = "js-my-textarea", @class = "ckeditor" })

Image browser in CKEditor needs the JSON result in a format like this:

[

        {

                "image": "/image1.jpg",

                "thumb": "/image1_thumb.jpg"

        },

        {

                "image": "/image2_.jpg",

                "thumb": "/image2_thumb.jpg"                

        },

]

To create a JSON file like this in MVC, make JsonResult with the same output:

 public JsonResult BrowseImages()

        {

            var imageBrowser = new List<ImageBrowserViewModel>();

            var path="/Images/"

            var images = Directory.GetFiles(Server.MapPath(path)).OrderByDescending(d => new FileInfo(d).CreationTime);

            foreach (var image in images)

            {

                imageBrowser.Add(new ImageBrowserViewModel

                {

                    image = string.Format(finalPath + Path.GetFileName(image)),

                    thumb = string.Format(finalPath + "Thumb/" + Path.GetFileName(image)),

                });

            }

            return Json(imageBrowser, JsonRequestBehavior.AllowGet);

        }

And the ImageBrowserViewModel is just a ViewModel to initiate the output:

 public class ImageBrowserViewModel

    {

        public string image { get; set; }

 

        public string thumb { get; set; }

    }

As you can see in my code and also based on the required JSON output, I save the thumb of my images to show them in image browser to pick. Now you can see the output of your plugin in your editor:

  



Handling custom error in mvc

In asp.net MVC there is a custom error in web.config like below:

<customErrors mode="On">

<error statusCode="404" redirect="~/404.html"/>

</customErrors>

 

Sometimes you want to redirect the user to your own custom error controller and log the error message. You can handle it! Firstly create your own Error controller:

 

public class ErrorController : Controller

    {

        public ActionResult HttpError404()

        {

            return View("HttpError404");

        }



        public ActionResult HttpError500()

        {

            return View("HttpError500");

        }



        public ActionResult General()

        {

            return View("General");

        }

    }

 

Secondly, you need to handle this controller in a higher level in your application, refer to global.asax and create the Application_Error method with the following definition:

protected void Application_Error(object sender, EventArgs e)

        {

            Exception exception = Server.GetLastError();        

            Response.Clear();

            HttpException httpException = exception as HttpException;

            RouteData routeData = new RouteData();

            routeData.Values.Add("controller", "Error");



            if (httpException != null)

            {

                switch (httpException.GetHttpCode())

                {

                    case 404:

                        routeData.Values.Add("action", "HttpError404");

                        break;

                    case 500:

                        routeData.Values.Add("action", "HttpError500");

                        break;

                    default:

                        routeData.Values.Add("action", "General");

                        break;

                }

            }



            routeData.Values.Add("error", exception);

            Server.ClearError();

            Response.TrySkipIisCustomErrors = true;

            IController errorController = new ErrorController();

            errorController.Execute(new RequestContext(new HttpContextWrapper(Context), routeData));

        }

 

Done! Now you can create your own Views of ErrorController and have your handled ErrorController.



Protecting the CDN application files from unauthorized requests

Recently I faced an issue about the protection of the files which were in a separated Web Application as a CDN. The files would be protected not only from external requests but also the internal unauthorized requests. The only way I had, was determining the valid request from the authorized IP addresses. I mean that not every request from the application for CDN was not valid. So I created a cryptography helper class with encrypting and decrypt methods to send the request between two applications:

 

 

static class Cryptography
    {
        private const int Keysize = 256;
        private const int DerivationIterations = 1000;

        public static string Encrypt(string plainText, string passPhrase)
        {
            var saltStringBytes = Generate256BitsOfRandomEntropy();
            var ivStringBytes = Generate256BitsOfRandomEntropy();
            var plainTextBytes = Encoding.UTF8.GetBytes(plainText);
            using (var password = new Rfc2898DeriveBytes(passPhrase, saltStringBytes, DerivationIterations))
            {
                var keyBytes = password.GetBytes(Keysize / 8);
                using (var symmetricKey = new RijndaelManaged())
                {
                    symmetricKey.BlockSize = 256;
                    symmetricKey.Mode = CipherMode.CBC;
                    symmetricKey.Padding = PaddingMode.PKCS7;
                    using (var encryptor = symmetricKey.CreateEncryptor(keyBytes, ivStringBytes))
                    {
                        using (var memoryStream = new MemoryStream())
                        {
                            using (var cryptoStream = new CryptoStream(memoryStream, encryptor, CryptoStreamMode.Write))
                            {
                                cryptoStream.Write(plainTextBytes, 0, plainTextBytes.Length);
                                cryptoStream.FlushFinalBlock();
                                var cipherTextBytes = saltStringBytes;
                                cipherTextBytes = cipherTextBytes.Concat(ivStringBytes).ToArray();
                                cipherTextBytes = cipherTextBytes.Concat(memoryStream.ToArray()).ToArray();
                                memoryStream.Close();
                                cryptoStream.Close();
                                return Convert.ToBase64String(cipherTextBytes);
                            }
                        }
                    }
                }
            }
        }

        public static string Decrypt(string cipherText, string passPhrase)
        {
            var cipherTextBytesWithSaltAndIv = Convert.FromBase64String(cipherText);
            var saltStringBytes = cipherTextBytesWithSaltAndIv.Take(Keysize / 8).ToArray();
            var ivStringBytes = cipherTextBytesWithSaltAndIv.Skip(Keysize / 8).Take(Keysize / 8).ToArray();
            var cipherTextBytes = cipherTextBytesWithSaltAndIv.Skip((Keysize / 8) * 2).Take(cipherTextBytesWithSaltAndIv.Length - ((Keysize / 8) * 2)).ToArray();

            using (var password = new Rfc2898DeriveBytes(passPhrase, saltStringBytes, DerivationIterations))
            {
                var keyBytes = password.GetBytes(Keysize / 8);
                using (var symmetricKey = new RijndaelManaged())
                {
                    symmetricKey.BlockSize = 256;
                    symmetricKey.Mode = CipherMode.CBC;
                    symmetricKey.Padding = PaddingMode.PKCS7;
                    using (var decryptor = symmetricKey.CreateDecryptor(keyBytes, ivStringBytes))
                    {
                        using (var memoryStream = new MemoryStream(cipherTextBytes))
                        {
                            using (var cryptoStream = new CryptoStream(memoryStream, decryptor, CryptoStreamMode.Read))
                            {
                                var plainTextBytes = new byte[cipherTextBytes.Length];
                                var decryptedByteCount = cryptoStream.Read(plainTextBytes, 0, plainTextBytes.Length);
                                memoryStream.Close();
                                cryptoStream.Close();
                                return Encoding.UTF8.GetString(plainTextBytes, 0, decryptedByteCount);
                            }
                        }
                    }
                }
            }
        }

        private static byte[] Generate256BitsOfRandomEntropy()
        {
            var randomBytes = new byte[32];

            using (var rngCsp = new RNGCryptoServiceProvider())
            {
                rngCsp.GetBytes(randomBytes);
            }

            return randomBytes;
        }
    }

 

Then in the first application which was the main production, I modified the GetFile HtmlHeler like this:

 

 

public static MvcHtmlString GetFile(this HtmlHelper htmlHelper, string url, string extension, string contentName)
        {
            if (string.IsNullOrEmpty(url) || string.IsNullOrEmpty(extension) || string.IsNullOrEmpty(contentName))
            {
                return MvcHtmlString.Empty;
            }

            extension = extension.Replace(".", "");

            if (!url.StartsWith("http://"))
            {
                url = string.Format("http://" + url);
            }

            url = url.Replace('\\', '/').Replace("~/", string.Empty) + "/File/Browse/";
            var filePathWithoutCdnAddress = $"{string.Format(extension)}/{Path.GetFileNameWithoutExtension(contentName)}{string.Format("." + extension)}";
            var userIpAddress = HttpContext.Current.Request.UserHostAddress;
            var saltKey = "E88CA429-406A-47B1-BBD7-8F9668B74DA8";
            var encryptedDate = Cryptography.Encrypt(filePathWithoutCdnAddress, userIpAddress + saltKey);
            return MvcHtmlString.Create(url + encryptedDate);
        }

 

As the direction of files is extension>contentName in CDN, so I had to send them separately to the GetFile HtmlHelper by the information about the file I had to get from the database of application. And saltKey is just for making the key more secure by the combination of user IP address. And finally in CDN application which also was an MVC template, I created an action named Browse with a parameter to get the encrypted request:

 

 

  public class FileController
    {
        public ActionResult Browse(string encryptedDate)
        {
            var userIpAddress = System.Web.HttpContext.Current.Request.UserHostAddress;
            var saltKey = "E88CA429-406A-47B1-BBD7-8F9668B74DA8";
            var decryptedDate = Cryptography.Decrypt(encryptedDate, userIpAddress + saltKey);
            var extension = decryptedDate.Split('/')[1];
            var contentName = decryptedDate.Split('/')[2];
            var dir = Server.MapPath(string.Format("/" + extension + "/" + contentName));
            var path = Path.Combine(dir);
            return base.File(path, extension);
        }
    }

 

 

As you can see, only the authenticated and authorized user can get the file from CDN and not everybody can make a request from the browser because the IP address is Unique. You can handle the unauthorized requests and redirect them to page in CDN! I would appreciate if you suggest another solution if you know, thanks!



Redirect to an action from baseController in MVC

In Asp.net MVC, in order to redirect to an action you can call RedirectToAction(actionName, ControllerName) easily. But if you would have to do it in BaseController, it doesn't work! For example, if you want to check the nullability of the user session you should initialize the Result of filterContext like this:

protected override void OnActionExecuting(ActionExecutingContext filterContext)

{

if (CurrentUser == null)

        filterContext.Result = new RedirectResult(Url.Action("Login", "Account"));

base.OnActionExecuting();

}

Or maybe in OnException method on MVC:

     protected override void OnException(ExceptionContext filterContext)

        {

               filterContext.HttpContext.Response.Clear();

               filterContext.Result = new RedirectResult(Url.Action("Login", "Account"));

               requestContext.HttpContext.Response.End();

               base.OnException(filterContext);

        }

Cheers!



Multiple submit buttons in asp.net MVC

In asp.net MVC you can submit a form by @using(Html.BeginForm()). You can Post the model you have bound via this operation and command the beginning form to post to a Post action: 

@using(Html.BeginForm("Create","FooController")).

Now if you have more than one submit button to send the page content to different post action, there are few ways: one of them is to post the content to a specific action and then decide in it to redirect which action:

 Razor:

<input type="submit" value="Field1" name="submitButton"  />

<input type="submit" value="Field2" name="submitButton"  />

 

Controller:

         public ActionResult SpecificOne(DTO dTO, string submitButton)

         {

                switch (submitButton)

                {

                    case "Field1":

                            return RedirectToAction("FirstCreate")

                    case "Field2":

                              return RedirectToAction("SecondCreate")

                 }

          }

 

But the way that I like it more is to use the attribute in post actions, this attribute would be like this:

   [AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = true)]

    public class HttpParamActionAttribute : ActionNameSelectorAttribute

    {

        public string Name { get; set; }



        public override bool IsValidName(ControllerContext controllerContext, string actionName, MethodInfo methodInfo)

        {

            return controllerContext.HttpContext.Request[Name] != null;

        }

    }

 

 

Now in your razor view, you should give a name to your buttons (names should be the same as your target actions) :

@model DTO 

@using(Html.beginForm(null, null))

{

   <button type="submit" name="FirstCreate" >First</button>

   <button type="submit" name="SecondCreate" >Second</button>        

}

And about the actions you should use the mentioned attribute and give the button name property:

        [HttpPost]

        [HttpParamAction(Name = "FirstCreate")]

        public ActionResult FirstCreate()

        {

            return View();

        }





        [HttpPost]

        [HttpParamAction(Name = "FirstCreate")]

        public ActionResult SecondCreate()

        {

            return View();

        }

 

Good luck!



About Me

Ehsan Ghanbari

Hi! my name is Ehsan. I'm a developer, passionate technologist, and fan of clean code. I'm interested in enterprise and large-scale applications architecture and design patterns. I spend a lot of time on software architecture. Since 2008, I've been as a developer for different companies and organizations and I've been focusing on Microsoft ecosystem all the time. During the past years, Read More

Post Tags
Pending Blog Posts
using Elmah in asp.net MVC4
Using FluentSecurity in MVC
Strategic design
Factory Pattern
time out pattern in ajax
Redis as a cache server
How to use PagedList In asp.net MVC
Multiple submit buttons in asp.net MVC
Domain driven design VS model driven architecture
What's the DDD-lite?