MVC3: Form submitted only by GET method, without specifying controller and action

Sometimes in ASP.Net MVC you need to submit a form that will always go by GET method. But standard BeginForm() function does not have an override that does not requires specifying Controller and Action where you would like to submit the form back to. And sometimes you don’t want to specify where the form to go to, cause the same form can go into multiple controllers (think of a partial view). And default BeginForm() submits only by POST. So there is no default way to say I want to submit form by POST to wherever the form has came from.

I had to poke through MVC source code to figure out how to do what I want, and keep all the parameters and not mess up any paths. Here you go:

public static MvcForm BeginForm(this HtmlHelper htmlHelper, FormMethod method)
{
    var rd = htmlHelper.ViewContext.HttpContext.Request.RequestContext.RouteData;
    var currentAction = rd.GetRequiredString("action");
    var currentController = rd.GetRequiredString("controller");

    return htmlHelper.BeginForm(currentAction, currentController, rd.Values, method);
}

Read Only User in MVC3 with minimal changes

I had a task of creating a read-only user role in our MVC application. And given the limited time for the task I had to do that with minimal changes to the domain/architecture.

I have started with creating “Readonly” user role. Hopefully I don’t need to explain how to do that.

This is not the most elegant solution to the problem, but it kinda does the job. It block read-only user from using the POST actions, also if available substitutes Edit views with Details views (presuming this is CRUD application). Also we add javascript to Edit pages where we don’t have Details page.

Then I created a global filter:


    public class ReadOnlyFilter : IActionFilter 
    {
        public void OnActionExecuting(ActionExecutingContext filterContext)
        {
            var currentUser = HttpContext.Current.User;
            if (currentUser == null || !currentUser.Identity.IsAuthenticated)
                return; // user is not logged in yet. Give them a break, please! Maybe they just trying to login. Not even touching your precious controllers yet!


            if (!currentUser.IsInRole("Readonly")) 
                return; // user is not read-only. Nothing to see here, move on!


            // Presume User is read-only from now on************************************

            var readOnlyAttribute = GetForReadOnlyUserAttribute(filterContext);

            // If DisableUserInput applied to the action, turn on JS for disabling the input fields on the view
            if (readOnlyAttribute != null && readOnlyAttribute.DisableInputFields)
                DisableInputOnForm(filterContext);

            // if AllowPost Attribute applied to the action, ignore the rest of this filter and get on with this
            if (readOnlyAttribute != null && readOnlyAttribute.AllowPost == true)
                return;

            // if action is of type post - deny
            if (filterContext.HttpContext.Request.HttpMethod.ToLower() == "post" )
            {
                ReadOnlyPage(filterContext);
                return;
            }

            // if action is "Create" - deny access
            if (filterContext.ActionDescriptor.ActionName.ToLower() == "create")
            {
                ReadOnlyPage(filterContext);
                return;
            }

            // if action is edit - check if Details action exits -> redirect to it.
            if (filterContext.ActionDescriptor.ActionName.ToLower() == "edit")
            {
                // find a method named Details that has exactly the same set of parameters as the request.
                var detailsAction = GetActionMethod(filterContext, "Details");

                // if "Details" action exist, we redirect the user into that action.
                if (detailsAction != null)
                {
                    // (in)sanity check. Also Resharper told me this could be null.
                    if (filterContext.HttpContext.Request.Url == null) return;

                    // simply replace the "Edit" with "Details" in a url. This is DIRTY, just the way I like my women: http://goo.gl/wrKuI
                    var currentUrl = filterContext.HttpContext.Request.Url.ToString();
                    var newUrl = currentUrl.Replace("Edit", "Details");

                    // and redirect to the new url.
                    filterContext.Result = new RedirectResult(newUrl);
                }
                else
                {
                    DisableInputOnForm(filterContext);
                }
            }
        }



        /// 
        /// Redirect user to read-only message page
        /// 
        /// 
        private void ReadOnlyPage(ActionExecutingContext filterContext)
        {
            filterContext.Result = new RedirectResult("~/ReadOnlyAccess");
        }


        /// 
        /// Activate javascript to disable all input fields.
        /// 
        private void DisableInputOnForm(ActionExecutingContext filterContext)
        {
            filterContext.Controller.ViewBag.DisableAllInputElements = true;
        }


        /// 
        /// If ForReadOnlyUserAttribute is applied for a method, return that attribute object.
        /// If actionName provided, work with that action in the controller. Otherwise work with currently executed action.
        /// 
        private ForReadOnlyUserAttribute GetForReadOnlyUserAttribute(ActionExecutingContext filterContext, String actionName = null)
        {
            MethodInfo method = GetActionMethod(filterContext, actionName);
            if (method == null) return null;

            var readOnlyAttribute = (ForReadOnlyUserAttribute)Attribute.GetCustomAttribute(method, typeof(ForReadOnlyUserAttribute));

            return readOnlyAttribute;
        }


        /// 
        /// Return MethodInfo for the executed action on the controller.
        /// If actionNameString is provided we return MethodName for the provided name.
        /// If no actionNameString is provided, we return the currently executed Action.
        /// 
        private MethodInfo GetActionMethod(ActionExecutingContext filterContext, String actionNameString = null)
        {
            var controllerType = filterContext.Controller.GetType();

            // get all the parameters from the original request. Copy Types of parameters in an array. 
            var paramTypes = filterContext.ActionParameters
                .Values
                .Where(p => p != null)
                .Select(o => o.GetType())
                .ToArray();

            var actionName = actionNameString ?? filterContext.ActionDescriptor.ActionName;

            // find a method that has exactly the same set of parameters as the request.
            var detailsAction = controllerType.GetMethod(actionName, paramTypes);

            return detailsAction;
        }


        public void OnActionExecuted(ActionExecutedContext filterContext)
        {
            // 1960: "I have a great idea! lets have every person in the country carry a radio tracking beacon!" 
            //  "That'll never fly!"  
            //
            // 2012: "I can has TWO iphones??"
        }
    }

That class is registered in Global.asax.cs:

        public static void RegisterGlobalFilters(GlobalFilterCollection filters)
        {
            filters.Add(new ReadOnlyFilter());
        }

And you need ForReadOnlyUserAttribute.cs:

    [AttributeUsage(AttributeTargets.Method, AllowMultiple = false) ]
    public class ForReadOnlyUserAttribute : Attribute
    {
        public bool DisableInputFields { get; set; }
        public bool AllowPost { get; set; }
    }

And in your global Layout page you need to add following block of code for javascript. Taking you already have jQuery in your project:

    @{  // if we are working with read-only user and Edit page does not have Details alternative, we show Edit page, but with all the controls disabled.
        var disable = (bool?) ViewBag.DisableAllInputElements;  // this is set in ReadOnlyFilter
        if (disable.HasValue && disable.Value == true)
        {
            //script from asawyer: http://stackoverflow.com/a/12477283/809357
            
        }
    }

To disable input forms on the pages, add [ForReadOnlyUser(DisableInputFields = true)] attribute on the controller action:

        [ForReadOnlyUser(DisableInputFields = true)]
        public virtual ViewResult Details(int id)
        {
            var model = _personRepository.Find(id);
            return View(model);
        }

To allow POST actions add [ForReadOnlyUser(AllowPost = true)] to actions where you take POST submit.
Beware – this allows unauthorised users to do POST actions. That was fine in my case, but might not be acceptable for you, so change the filter code a bit.

And that seems to be it.

Generate QR Barcode in ASP.Net MVC

I had a task of creating a QR barcode and displaying it on a web-page from ASP.Net MVC system. There are a lot of free web-services for generating a qr-codes for you, ( like http://qrcode.kaywa.com/ ) But this time I did not want to use a service for various reasons. I had to get the barcode generated on my server. (Just accept it as default!)

I have found many .Net libraries that generate a qr-barcodes, but for some reason I did like QrCode.Net. Probably because it is hosted on codeplex and I keep my open-source projects there as well.

I have downloaded their Dll file, added it as a reference to my MVC project:

In my MVC project I have added a new action that returned a FileStremResult:

using Gma.QrCodeNet.Encoding;
using Gma.QrCodeNet.Encoding.Windows.Controls;

public virtual FileResult BarcodeImage(String barcodeText)
{
    // generating a barcode here. Code is taken from QrCode.Net library
    QrEncoder qrEncoder = new QrEncoder(ErrorCorrectionLevel.H);
    QrCode qrCode = new QrCode();
    qrEncoder.TryEncode(barcodeText, out qrCode);
    Renderer renderer = new Renderer(5, Brushes.Black, Brushes.White);

    // write to file if required for auditing
    //renderer.CreateImageFile(qrCode.Matrix, String.Format(@"d:\tmp\{0}.png", barcodeText), ImageFormat.Png);

    Stream memoryStream = new MemoryStream();
    renderer.WriteToStream(qrCode.Matrix, memoryStream, ImageFormat.Png);

    // very important to reset memory stream to a starting position, otherwise you would get 0 bytes returned
    memoryStream.Position = 0;

    var resultStream = new FileStreamResult(memoryStream, "image/png");
    resultStream.FileDownloadName = String.Format("{0}.png", barcodeText);

    return resultStream;
}

UPDATE 23 Dec 2013: Since this article was written, the API of QrCode.Net has changed slightly and the code above does no longer compile. So see the new code:

using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Web.Mvc;
using Gma.QrCodeNet.Encoding;
using Gma.QrCodeNet.Encoding.Windows.Render;

namespace SampleBarCodeMvc.Controllers
{
    public class HomeController : Controller
    {
        public ActionResult Index()
        {
            return View();
        }

        public ActionResult BarcodeImage(String barcodeText)
        {
            // generating a barcode here. Code is taken from QrCode.Net library
            QrEncoder qrEncoder = new QrEncoder(ErrorCorrectionLevel.H);
            QrCode qrCode = new QrCode();
            qrEncoder.TryEncode(barcodeText, out qrCode);
            GraphicsRenderer renderer = new GraphicsRenderer(new FixedModuleSize(4, QuietZoneModules.Four), Brushes.Black, Brushes.White);

            Stream memoryStream = new MemoryStream();
            renderer.WriteToStream(qrCode.Matrix, ImageFormat.Png, memoryStream);

            // very important to reset memory stream to a starting position, otherwise you would get 0 bytes returned
            memoryStream.Position = 0;

            var resultStream = new FileStreamResult(memoryStream, "image/png");
            resultStream.FileDownloadName = String.Format("{0}.png", barcodeText);

            return resultStream;
        }
    }
}

Then in one of the views I place this image tag:

  <img src="/Controller/BarcodeImage?barcodeText=Hello"/>

Where Controller is a name of your controller where you placed the action. And that will give you an image with a barcode and word “Hello” encoded in it.

I have quickly created a sample solution in Visual Studio 2013 (you should be able to open that in VS2012):
SampleBarCodeMvc – this is to show a working example.

Enjoy!

Update: QrCode.Net is now available via nuget package and can be installed either via Nuget inside of your Visual Studio or in VS-console: Install-Package QrCode.Net

ASP.Net MVC: Autosave drop-down with notification

I did spend quite a time figuring out jQuery verbs to do just what I needed. So I’ll share this with my blog and possible readers. On my view I had a model written out, some other stuff, but here I’m only interested in DropDown and JavaScript bits:

@Html.DropDownListFor(m => model.tagId, new SelectList(ViewBag.Tags, "Value", "Text", model.tagId), new {@class = "document-category", data_id = model.modelId})

where

ViewBag.Tags was populated in controller like this:

ViewBag.Tags = _tagRepository.All
    .OrderBy(t => t.Name)
    .Select(t => new { Text = t.Name, Value = t.TagId.ToString() })
    .OrderBy(t => t.Text)
    .ToList();

Back to the view. Added JavaScript like this one:

$(document).on("change", ".document-category", function () {
    var select = $(this);
    $.ajax({
        type: "POST",
        url: "@(Url.Action("Action", "Controller"))",
        data: { id: select.data("id"), 
        tagId: $("option:selected", select).val()},
        error: function () {
            select.after("Error occurred");
        },
        success: function (data) {
            if (data.Success === true) {
                select.after(function () {
                    return $('<div> Saved </div>').delay(1000).fadeOut(1000);
                });
            } else {
                select.after("Error occurred");
            }
        }
    });
});

The very first line can be replaced by $(".document-category").change(function(){ but this time I had to deal with other java-script library that was stripping-bare my stuff. On the other end of the ajax request you need to put controller action:

[HttpPost]
public virtual JsonResult Action(int id, int? tagId)
{
        //blah, do some update stuff
    return Json(new {Success = true, Message = "Updated"});
}

Disable unobtrusive validation on per-element basis in ASP.Net MVC

Sometimes you just don’t want a validation on your form elements, but the framework just adds the validation automatically, cause it is too clever. To disable the validation on one element, pass html parameter data_val=false to your controller and JavaScript will ignore the that field. Example:

@Html.DropDownList("documentId", new SelectList(ViewBag.MetaTags, "Value", "Text"), new {data_val=false})

Convert Due Date to human readable format

Sometimes you need to show date in a “Due in X-days” format, not just a date. This will convert date to a string with granularity: Today, tomorrow, yesterday, days, weeks, months.

public static class HumanTime
{
    public static string DueDate(DateTime dueDate) {
        return DueDate(dueDate, DateTime.Now);
    }

    public static string DueDate(DateTime dueDate, DateTime dateNow) {
        DateTime nowDate = dateNow;

        TimeSpan ts = nowDate - dueDate;

        if (dueDate.Date == DateTime.Today)
        {
            return "due today";
        }

        if (dueDate.Date == DateTime.Now.AddDays(1).Date)
        {
            return "due tomorrow";
        }

        if (dueDate.Date == DateTime.Now.AddDays(-1).Date)
        {
            return "was due yesterday";
        }


        int totalDays = (int)Math.Round(ts.TotalDays);
        if (Math.Abs(totalDays) &lt; 7)
        {
            return DueStringFormat(totalDays, "day");
        }

        int weeks = (int)Math.Round( ts.TotalDays / 7 );
        if (Math.Abs(weeks) &lt; 5 && Math.Abs(totalDays)&lt;30 )
        {
            return DueStringFormat(weeks, "week");
        }

        int months = (int)Math.Round( ts.TotalDays / 30 );
        return DueStringFormat(months, "month");
    }


    public static string DueStringFormat(int count, string unit)
    {
        string format = "{0} {1}";
        if (count &lt; 0)
        {
            format = "due in {0} {1}";
        } 
        else
        {
            format = "{0} {1} overdue";
        }

        return String.Format(format, Math.Abs(count), unit.Quantify(count));
    }


    /// Adds "s" suffix to a word if there are more than one thing involved.
    public static string Quantify(this String singular, int count)
    {
        int abs = Math.Abs(count);
        if (abs > 1)
            return singular+"s";
        else 
            return singular;
    }
}

Enums and Display attribute

Enums are handy. But they do not allow you have easy Text description on them.
How about using DisplayAttribuite on enum values and have that text in a drop-down? Easy!

/// <summary>
/// Creates a SelectList from Enum, taking Description values from Enum fields
/// Taken from here: http://stackoverflow.com/a/3705387/809357
/// </summary>
public static SelectList ToSelectListWithDisplayName<T>(this T enumeration, string selected = "")
{
    var source = Enum.GetValues(typeof(T));

    var items = new Dictionary<object, string>();

    var displayAttributeType = typeof(DisplayAttribute);

    foreach (var value in source)
    {
        FieldInfo field = value.GetType().GetField(value.ToString());

                    if (field == null) continue;

        DisplayAttribute attrs = (DisplayAttribute)field.GetCustomAttributes(displayAttributeType, false).FirstOrDefault();
        if (attrs != null)
        {
            items.Add((int)value, attrs.GetName());
        }else   // in case Description attribute is not available, we fall back to the default name
        {
            items.Add((int)value, value.ToString());
        }
    }
    return new SelectList(items, "Key", "Value", selected);
}

Also you can get just on Display Name for one Enum Value:

public static string GetDisplayName(this Enum value)
{
    FieldInfo field = value.GetType().GetField(value.ToString());

    if (field == null)
        return String.Empty;

    object[] attribs = field.GetCustomAttributes(typeof(DisplayAttribute), true);
    if(attribs.Length > 0)
    {
        return ((DisplayAttribute)attribs[0]).GetName();
    }
    return value.ToString();
}

Creating custom Html Helper in MVC3

Creating custom attribute is pretty simple really, just follow the example. You need to return MvcHtmlString – that is string with your html to be displayed on a page – this will not be escaped.

public static MvcHtmlString DisplayWithNameFor<TModel, TProperty>(
    this HtmlHelper<TModel> htmlHelper,Expression<Func<TModel, TProperty>> expression)
{
    ModelMetadata metaData = ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData);
    if (metaData.Model == null)
    {
        return MvcHtmlString.Create(String.Empty);
    }

    string html = "<div class=\"display-label\">";
    html += htmlHelper.LabelFor(expression).ToString();
    html += "</div><div class=\"display-field\">";
    html += htmlHelper.DisplayFor(expression).ToString();
    html += "</div>";

    return MvcHtmlString.Create(html);
}

MVC Recipies: On Page Action Return to the previous page

Whenever you need a page to return to the previous page when you get an form submit or some other action, this can be used:

Have a base controller that is inherited by all your controllers. That is always a good idea – you can easily add things that work site-wide.
In you base controller have a OnActionExecuting overriden – here we are going to store the previous page.
Something like this:

public abstract partial class MyController : Controller
{

public ActionResult RedirectToPrevious(String defaultAction, String defaultController)
{
  if (Session == null || Session["PrevUrl"] == null)
  {
      return RedirectToAction(defaultAction, defaultController);
  }

  String url = ((Uri)Session["PrevUrl"]).PathAndQuery;

  if (Request.Url != null && Request.Url.PathAndQuery != url)
  {
      return Redirect(url);
  }

  return RedirectToAction(defaultAction, defaultController);
}


protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
    var httpContext = filterContext.HttpContext;

    if (httpContext.Request.RequestType == "GET"
        && !httpContext.Request.IsAjaxRequest()
        && filterContext.IsChildAction == false)    // do no overwrite if we do child action.
    {
        // stop overwriting previous page if we just reload the current page.
        if (Session["CurUrl"] != null 
            && ((Uri)Session["CurUrl"]).Equals(httpContext.Request.Url) ) 
            return;

        Session["PrevUrl"] = Session["CurUrl"] ?? httpContext.Request.Url;
        Session["CurUrl"] = httpContext.Request.Url;
    }
}
}

Now, as I figured, it is not the best idea for redirecting everything by default. For validation purposes you would like not to redirect

Then you can reference Session[“PrevUrl”] in controllers, but there is a better way of doing it – create attribute!

public class RedirectToPreviousPageAttribute : ActionFilterAttribute
{
///

/// Place [RedirectToPreviousPage] attribute on Controller action
/// and when you do submit of form, this functionality will kick in.
/// We check if Session has Previous URL set and force to redirect
/// to whatever it says.
/// If no Session variable is set, just do what it is supposed to do in the first place.
///

/// public override void OnResultExecuted(ResultExecutedContext filterContext)
{
var httpContext = filterContext.HttpContext;

    if (httpContext.Session == null || httpContext.Session["PrevUrl"] == null)
    {
        base.OnResultExecuted(filterContext);
        return;
    }

    String url = ((Uri)httpContext.Session["PrevUrl"]).PathAndQuery;

    if (httpContext.Request.Url != null && httpContext.Request.Url.PathAndQuery != url)
    {
        httpContext.Response.Redirect(url);
    }


    base.OnResultExecuted(filterContext);

}

}

And now, wherever you are put this attribute on a controller action (or entire controller), it will redirect to the previous page:

[HttpPost]
public virtual ActionResult Edit(Model model)
{
    if (!ModelState.IsValid)
    {
        return View(model);
    }

    _repository.InsertOrUpdate(model);
    _repository.Save();

    return RedirectToPrevious("Index", "Controller");
}

VoilĂ ! Good to go!