:::: MENU ::::

Tuesday, October 14, 2014

 
C# 

        public ActionResult Async()
        {
            return View();
        }

        // Submit this page to begin processing
        [AcceptVerbs(HttpVerbs.Post)]
        public JsonResult Async(FormCollection collection)
        {
            try
            {
                // some test data
                var records = new ArrayList();
                for (var i = 0; i < 10; i++)
                    records.Add(i);

                // async storage (must be at app level?)
                HttpContext.Application["RecordsTotal"] = records.Count;
                HttpContext.Application["RecordsProcessed"] = 0;

                var caller = new AsyncProcessCaller(AsyncProcess);
                caller.BeginInvoke(records, null, null);

                return Json(new { Result = "Started..." }, JsonRequestBehavior.AllowGet);
            }
            catch (Exception ex)
            {
                return Json(new { Exception = ex.ToString() }, JsonRequestBehavior.AllowGet);
            }
        }

        // provide a signature for executing asynchronusly
        public delegate void AsyncProcessCaller(ArrayList records);

        // the actual method that does the work
        public void AsyncProcess(ArrayList records)
        {
            // do stuff with the records
            foreach (var record in records)
            {
                System.Threading.Thread.Sleep(1000); // pretend we did labor intesive work
                HttpContext.Application["RecordsProcessed"] = (int)HttpContext.Application["RecordsProcessed"] + 1;
            }
        }

        // a method that returns the status, call this with javascript to update your status bar
        public JsonResult AsyncStatus()
        {
            var total = (int)HttpContext.Application["RecordsTotal"];
            var processed = (int)HttpContext.Application["RecordsProcessed"];
            var percent = Math.Round(((decimal)processed / (decimal)total) * 100, 0);

            return Json(new { PercentComplete = percent }, JsonRequestBehavior.AllowGet);
        }
 
 
HTML

 

       

            0%
       

   

   
   

     

Thursday, October 9, 2014

Most people coming to ASP.NET MVC tend to have similar problems. Most of them are related to refreshed understanding of HTML+HTTP limitations. Some are mitigated by ASP.NET MVC (checkboxes), but a lot require custom solutions.

One of the more common problems I get asked about by every second person learning MVC is how to make a form with two independent submit buttons, where both should submit the same data, but a have a different processing logic. For example, form may have a Save Draft button and Publish button.

Until now, I always said that it is possible, Google/StackOverflow and find out. But I just had to build such kind of thing myself, so I finally took some time to find/build the final solution.
So, let̢۪s look at the solutions available online.

StackOverflow question "How do you handle multiple submit buttons in ASP.NET MVC Framework?" is the starting point, but it does not provide a good solution for the situation where you need to send same data for both buttons, except the solution to switch by button name, which is duct-taping.

Post "ASP.NET MVC – Multiple buttons in the same form" by David Findley is much more interesting, since his AcceptParameterAttribute is very similar to my solution. However, this has several (small) shortcomings: first, you have to specify what is actually an action you want to do in an attribute. So even if you name your action â€Å“SaveDraft”, you will still need to specify AcceptParameter(Name=â€Å“button”, Value=â€Å“saveDraft”). Another thing is need to put [ActionName] on your actions, which is understandable, but a bit confusing for people who do not yet know the idea.

So, I wanted to build the solution that would require at most one attribute, and where the name of action method corresponds to the attributes of the button. Also, since value is the thing being shown in the button, and

Now, the solution. Basically, instead of using ActionMethodSelectorAttribute, I am using ActionNameSelectorAttribute, which allows me to pretend the action name is whatever I want it to be. Fortunately, ActionNameSelectorAttribute does not just make me specify action name, instead I can choose whether the current action matches request.
So there is my class (btw I am not too fond of the name):

public class HttpParamActionAttribute : ActionNameSelectorAttribute {
    public override bool IsValidName(ControllerContext controllerContext, string actionName, MethodInfo methodInfo) {
        if (actionName.Equals(methodInfo.Name, StringComparison.InvariantCultureIgnoreCase))
            return true;

        if (!actionName.Equals("Action", StringComparison.InvariantCultureIgnoreCase))
            return false;
        
        var request = controllerContext.RequestContext.HttpContext.Request;
        return request[methodInfo.Name] != null;
    }
}
 
How to use it? Just have a form similar to this:
<% using (Html.BeginForm("Action", "Post")) { %>
  <!— …form fields… -->
  <input type="submit" name="saveDraft" value="Save Draft" />
  <input type="submit" name="publish" value="Publish" />
<% } %>
 
and controller with two methods

public class PostController : Controller {
    [HttpParamAction]
    [AcceptVerbs(HttpVerbs.Post)]
    public ActionResult SaveDraft(…) {
        //…
    }

    [HttpParamAction]
    [AcceptVerbs(HttpVerbs.Post)]
    public ActionResult Publish(…) {
        //…
    }
}
 
As you see, the attribute does not require you to specify anything at all. Also, name of the buttons are translated directly to the method names. Additionally (I haven't tried that) these should work as normal actions as well, so you can post to any of them directly.

There is some room for improvements (hard coded "action" as a default action name), but in general I am satisfied with this solution.

When creating a new MVC project in ASP.NET MVC 5, Bootstrap is already included. For some reason proper formatting for form errors (the red colored error message and the red border around the controls) are not working. There are loads of articles and blog posts how to change this and that to enable this, but in ASP.NET MVC 5, the only thing you actually have to do is add a few classes to your Site.css file. Why they aren’t in there from the outset I don’t know.
Site.css
/* styles for validation helpers */
.field-validation-error {
    color: #b94a48;
}

.field-validation-valid {
    display: none;
}

input.input-validation-error {
    border: 1px solid #b94a48;
}


select.input-validation-error {
    border: 1px solid #b94a48;
}

input[type="checkbox"].input-validation-error {
    border: 0 none;
}

.validation-summary-errors {
    color: #b94a48;
}

.validation-summary-valid {
    display: none;
}
Sample form.cshtml
@model WebApplication6.Models.TestModel

@{
    ViewBag.Title = "Home Page";
}




@using (Html.BeginForm("Index", "Home", FormMethod.Post, new { @class = "form-horizontal", role = "form" }))
{
    
class
="form-group">
@Html.LabelFor(m => m.Name, new { @class = "col-md-2 control-label" })
class
="col-md-10">
@Html.TextBoxFor(m => m.Name, new { @class = "form-control" })


@Html.ValidationMessageFor(m => m.Name)


class
="form-group">
@Html.LabelFor(m => m.GenderId, new { @class = "col-md-2 control-label" })
class
="col-md-10">
@Html.DropDownListFor(m => m.GenderId, new SelectList(Model.Genders, Model.GenderId), "", new { @class = "form-control" })


@Html.ValidationMessageFor(m => m.GenderId)




class
="form-group">
class
="col-md-offset-2 col-md-10">
"submit" class="btn btn-default" value="OK" />


}


Sample TestModel.cs
public class TestModel
{
   [Required(ErrorMessage = "Name is required")]
   [MinLength(3, ErrorMessage = "Name must be at least 3 letter")]
   public string Name { get; set; }
   [Display(Name= "Gender")]
   [Required(ErrorMessage = "Gender is required")]
   public string GenderId { get; set; }

   public string[] Genders = new[] {"Male", "Female"};
}
More