top button
Flag Notify
    Connect to us
      Site Registration

Site Registration

Passing Data From One Controller To Another In ASP.NET MVC

+4 votes
612 views

At times you need to pass data from an action method belonging to one controller to an action method belonging to another controller. There are three ways to accomplish this task. They are:

  • Pass data as query string parameters
  • Pass data in TempData dictionary
  • Pass data as route parameters

 Let's quickly see how each of these three approaches work.

Pass data as query string parameters

This approach is possibly the most primitive one and involves no special consideration from your side. The action method sending the data can use Redirect() method or RedirectToAction() method to transfer the control to the receiving action method. The following code shows how this is done:

public ActionResult Index()
{
    Customer data = new Customer()
    {
        CustomerID = 1,
        CustomerName = "Abcd",
        Country = "USA"
    };
    string url=string.Format("/home2/index?customerid={0}
               &customername={1}&country={2}",
               data.CustomerID,data.CustomerName,data.Country);
    return Redirect(url);
}

The above code shows Index() action from Home1 controller. The Index action method instantiates Customer object - the model class - that looks like this:

public class Customer
{
    public int CustomerID { get; set; }
    public string CustomerName { get; set; }
    public string Country { get; set; }
}

It then forms a URL pointing to the Index() action from Home2 controller. Notice how data from Customer object is transferred in the form of query string parameters. In the above example there is no logic for generating model data but in a more realistic case you may have such a logic in this method. And once the data is generated you can pass it to the another controller using query string. The Redirect() method then takes the control to the Index() action of Home2 controller.

The Index() action of Home2 can receive the data as shown below:

public ActionResult Index()
{
    Customer data = new Customer();
    data.CustomerID = int.Parse(Request.QueryString["CustomerID"]);
    data.CustomerName = Request.QueryString["CustomerName"];
    data.Country = Request.QueryString["Country"];
    return View(data);
}

As you can see Request.QueryString collection is being used to read the values passed in the query string. Once a Customer object is formed, it is passed to the Index view.

This technique has an advantage that it is quite simple and requires no additional configuration. However, it's bit crude technique and you should avoid it if any of the other techniques can be used.

Pass data using TempData dictionary

In this technique you store the data to be passed in the TempData dictionary in the sender action method. The receiving action method reads the data from the TempData dictionary. You might be aware that TempData dictionary can hold data till it is read and this can carry data across multiple requests. The following code shows how this is done:

public ActionResult Index()
{
    Customer data = new Customer()
    {
        CustomerID = 1,
        CustomerName = "Abcd",
        Country = "USA"
    };
    TempData["mydata"] = data;
    return RedirectToAction("Index", "Home2");
}

As you can see Index() action instantiates a Customer object as before. This time, however, it stores the Customer object in a TempData key named mydata. The RedirectToAction() method is then used to take the control to the Index() action of Home2 controller.

Inside the Index() of Home2, you can read the value as follows:

public ActionResult Index()
{
    Customer data = TempData["mydata"] as Customer;
    return View(data);
}

The above code reads the Customer object from TempData dictionary and passes it to the Index view.

The TempData technique doesn't require any additional setup but it requires that session state be enabled. Also, TempData is designed to store arbitrary pieces of data. If you are planning to send model objects through TempData, that may be a deviation from standard design practices.

Pass data as route parameters

In this technique you need to do an additional work of defining a route in the system. For example, if you wish to pass the Customer data in the form of route parameters you need to define a route like this:

routes.MapRoute(
    name: "Default2",
    url: "{controller}/{action}/
          {customerid}/{customername}/{country}",
    defaults: new { controller = "Home2", action = "Index" }
);

As shown above, the route includes {customerid}, {customername}, and {country} route parameters and the route is mapped with Index() action of Home2 controller. Once the above configuration is done you can pass data from the sender action as follows:

public ActionResult Index1()
{
    Customer data = new Customer()
    {
        CustomerID = 1,
        CustomerName = "Abcd",
        Country = "USA"
    };
    return RedirectToAction("Index", "Home2", data);
}

Notice that, this time the Customer object is passed as the third parameter of RedirectToAction() method. This way Customer data will be passed in the form of route parameter values. To receive this data the receiver action method should write something like this:

public ActionResult Index()
{
    Customer data = new Customer();
    UpdateModel(data);
    return View(data);
}

// OR

public ActionResult Index(Customer data)
{
    return View(data);
}

As shown above you can either use UpdateModel() method to transfer values from the route to the Customer object or you can have a parameter to the Index() action method.

This technique is quite clean and makes use of MVC specific parts (route and route parameters). It doesn't have any dependency on session state as in the case of TempData technique. On the other hand you need to create a route to deal with the route parameters.

Note about query string and route parameters

Before I conclude this post, it would be interesting to see a small thing about how RedirectToAction() deals with query string and route parameters.

Let's assume that your sender action is like this:

public ActionResult Index()
{
    Customer data = new Customer()
    {
        CustomerID = 1,
        CustomerName = "Abcd",
        Country = "USA"
    };
    return RedirectToAction("Index", "Home2", data);
}

Notice that RedirectToAction() method passes Customer data object to Index() of Home2 as the third parameter.

The interesting thing to note is - If you haven't defined any route to match the data MVC sends it as query string. And if you have defined a route, it passes the values as route parameters. You can confirm this by observing the browser address bar once the Index() of Home2 renders the view. The following figure shows the difference:

image

That also means in the query string technique discussed earlier, you could have used exactly same code in the receiving action as in the case of route parameter technique.

In addition to the three techniques discussed above you can also use Session object but that's not discussed here separately since TempData technique anyway depends on the session storage.

That's it for now. Keep coding!

posted Oct 19, 2016 by Shivaranjini

  Promote This Article
Facebook Share Button Twitter Share Button LinkedIn Share Button


Related Articles

Method overloading is very common technique used in C# code. Although it works great for normal C# classes, the same can't be said about ASP.NET MVC controller classes. If you ever tried to implement method overloading for MVC actions you are probably aware of the following exception:

image

Unlike an ordinary C# class, a controller is dealing with HTTP protocol. And HTTP doesn't understand overloading as C# does. So, in case you wish to use method overloading in a controller you need to consider the four possible ways as discussed in the remainder of this article.

Actions handling different HTTP verbs

In this scenario you have two action methods with the same name BUT they handle different HTTP verbs. Consider the following fragment of code:

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

[HttpPost]
public ActionResult Index(Customer obj)
{
    //do something with obj
    return View();
}

As you can see the first Index() action deals with the GET requests whereas the second Index() action deals with POST requests. The first version is parameter-less whereas the second accepts a parameter of type Customer (this depends on what you are submitting from the view. The above example assumes you are submitting customer data).

The above overloaded methods work fine since each is dealing with different HTTP verb.

Actions handling GET requests and attribute routing

You can overload actions dealing with GET requests if you configure attribute routing properly. The following example will make this clear:

[Route("Home/DoWork/{status:int}")]
public ActionResult DoWork(int status)
{
    return View();
}

[Route("Home/DoWork/{flag:bool}")]
public ActionResult DoWork(bool flag)
{
    return View();
}

Now in this case there are two DoWork() actions but one has an integer parameter whereas the other has a boolean parameter. Moreover, [Route] attribute is used to configure attribute based routing. Notice that the first [Route] attribute specifies that status parameter is int and the second specifies that it is bool. So, if a URL is /home/dowork/10 then it will be handled by the first DoWork() whereas if the URL is /home/dowork/true it will be handled by the second DoWork() action.

Make sure you call MapMvcAttributeRoutes() in the RouteConfig.cs file before you test this technique.

Overloaded actions marked as [NonAction]

In this technique you create a public action method as usual. And then create several overloads of it as that are not MVC actions. These overloads are marked as [NonAction] attribute. The following code fragment will make it clear:

[HttpPost]
public ActionResult Calculate()
{
    int i = int.Parse(Request.Form["num1"]);
    int j = int.Parse(Request.Form["num2"]);
    return Calculate(i , j);
}

[NonAction]
public ActionResult Calculate(int i, int j)
{
    return View( i + j );
}

Here, the first method - Calculate() - acts as an MCV action method. The Calculate() then invokes an overload - Calculate(int,int). The overload does its job and returns a view to the caller. Notice that the second Calculate() is marked with [NonAction] attribute. The [NonAction] attribute indicates that the method under consideration should not be treated as an action. Since the method is no longer an action it doesn't interfere in the working of HTTP and the controller.

[ActionName] attribute

 In this technique you create overloaded action methods as you would have done in any C# class. But give them another unique name using the [ActionName] attribute. The following code fragment will make it clear:

[HttpPost]
[ActionName("ProcessForm1")]
public ActionResult ProcessForm(Customer obj)
{
    return View();
}

[HttpPost]
[ActionName("ProcessForm2")]
public ActionResult ProcessForm(Employee emp)
{
    return View();
}

Here, we have two overloads of the ProcessForm() actions. However, both have [ActionName] attribute added on top of them. The [ActionName] attribute assigns a unique name to a method that is then used by the MVC framework. That means for your C# code the method name remains ProcessForm() but for the MVC framework (including routing engine and HTML helpers) they represent two methods - ProcessForm1() and ProcessForm2().

Remember that when you use [ActionName] attribute you are assigning a different name to the action method under consideration. In helpers such as BeginForm() and ActionLink() you should use the name as defined in the [ActionName] attribute (ProcessForm1 and ProcessForm2 in this case).

That's it! Although it is better to keep away from method overloading in the controllers, if at all you wish to implement overloading you can use one of the above ways. As you can see using overloading adds a bit of complexity to your controller.  But it is possible in a limited way if needed.

READ MORE

HTML5 custom data attributes (data-*) are used to store arbitrary pieces of metadata about an element. One way to store such metadata in data-* attributes is to create a separate data-* attribute for each piece of information you wish to store. This approach works well if there are only a few data-* attributes. However, at times you need to store a bunch of metadata in data-* attributes. In such cases instead of creating multiple data-* attributes you can create just one data-* attribute and store all the pieces of  metadata as an object in JSON format. To that end this article illustrates how custom data attributes can be used to store JSON data in an ASP.NET MVC application.

Consider the following view:

image

The above view shows a table that lists records from Customers table of Northwind database. Each row has Order Details button. Click on the Order Details button displays details such as OrderID and ShippingDate for the last order placed by the customer under consideration. These details are displayed in an alert dialog like this:

image

If you see the Customers table, it has only two columns - one showing CustomerID and the other showing CompanyName. From where does the order details such as OrderID and OrderDate came from? These details are stored in a custom data attribute named data-lastorder of each <tr> element. For example, consider the following markup that is revealed in the HTML source of the page in the browser:

image

As you can see the <tr> element has data-lastorder attribute that stores an object in JSON format. The object stores CustomerID, OrderID and OrderDate. The data-lastorder attribute can be accessed using jQuery code. The following jQuery code shows how this can be accomplished.

 $(document).ready(function () {
  $("input:button").click(function (evt) {
    var orderData = $(evt.target).closest("tr").data("lastorder");
    alert("Last Order : #" + orderData.OrderID + " on " + orderData.OrderDate);
  });
});

As you can see the ready() handler uses :button selector to match all the <input> elements of type button. This will return all the Order Details buttons shown in the earlier figure. The code then wires click event handler to the click event of the Order Details buttons. The click event handler finds <tr> element closest to the button being clicked. This is done using the closest() method. The data() method returns the value of a specified custom data attribute. In this case lastorder is specified (you can omit data- from the attribute name). The return value of data() in this case will be a JSON object and is stored in orderData variable. An alert dialog then displays the OrderID and OrderDate properties of the orderData object.

So far so good. But how to emit the data-lastorder attribute to the client browser? Obviously you can't hard-code it. It has to be generated on the fly using server side code. In this case you will have to write such code in the controller and the view.

Let's assume that you have Entity Framework data model for the Customers and Orders tables as shown below:

image

 

Also assume that you have HomeController with Index() method that fetches the data and makes it suitable to go inside data-lastorder attribute. The following code shows how Index() method can do this job:

public ActionResult Index()
{
  NorthwindEntities db = new NorthwindEntities();
  var customers = from c in db.Customers
                  where c.Country == "USA"
                  select c;
  List<Customer> customerList = customers.ToList();
  Dictionary<string, string> orderDict = new Dictionary<string, string>();
  foreach (Customer obj in customerList)
  {
    var order = (from o in db.Orders
                 where o.CustomerID == obj.CustomerID
                 orderby o.OrderDate descending
                 select o).FirstOrDefault();
    string jsonOrder = JsonConvert.SerializeObject(
                       new { CustomerID=order.CustomerID,
                             OrderID=order.OrderID,
                             OrderDate=order.OrderDate });
    orderDict.Add(order.CustomerID, jsonOrder);
  }
  ViewData["orderDict"] = orderDict;
  return View(customerList);
}

The above code creates a database context object (db). A LINQ to Entities query selects all the Customer objects with Country property equal to USA. Then a generic List of Customer objects is obtained by calling ToList() method of the customers variable. A dictionary is created to store order information for all the customers. The key of the dictionary is CustomerID and its value is JSON representation of the order details. Next, a foreach loop iterates through all the customer list. With every iteration the most recent Order is retrieved from the Orders DbSet for a CustomerID. This is done by sorting the results in descending order and then calling FirstOrDefault() method. Then comes the important part. The JsonConvert class of Json.NET library is used to serialize an anonymous object containing order details such as CustomerID, OrderID and OrderDate. The SerializeObject() method accepts an object and returns its JSON representation as a string. The JSON representation of the order data is stored in the dictionary created earlier. The customerList is passed to the Index view as its model whereas orderDict is passed to the view in a ViewData variable.

This completes the controller. The remaining part of the code goes inside the Index view and is shown below:

<%@ Page Language="C#" Inherits="System.Web.Mvc.ViewPage
<List<JSONObjectInCustomDataAttribute.Models.Customer>>" %>
...
<table border="1" cellpadding="6">
<% foreach(var customer in Model){ %>
<tr data-lastorder='<%= ((Dictionary<string,string>)ViewData["orderDict"])
                         [customer.CustomerID] %>'>
<td><%= customer.CustomerID %></td>
<td><%= customer.CompanyName %></td>
<td><input type="button" value="Order Details" /></td>
</tr>
<%}%>
</table>

As shown in the above code, @Page directive sets the model for the view to List<JSONObjectInCustomDataAttribute.Models.Customer>. Make sure to change the namespace of the data model class as per your setup. Then a <table> is generated by iterating through the Model. Each <tr> element emitted has data-lastorder attribute and its value is assigned from the orderDict ViewData variable. Recollect that orderDict is a dictionary where key is CustomerID and value is the order data in JSON format. Then CustomerID, CompanyName values are added to two table cells. The third table cell contains the Order Details button.

That's it! You can now run the application and see whether order data is available in data-lastorder attribute as expected.

READ MORE

Recently while developing a sample application in ASP.NET MVC, I came across a peculiar situation. There was a LINQ to SQL class with several extensibility methods defined (OnXXXXChanging kind of methods where XXXX is the name of a property). The methods were performing some validation checks and in case of any violation were throwing exceptions. The LINQ to SQL class was working fine. However, at some places I wanted to display the validation errors (thrown by the class as exceptions) on the ASP.NET MVC views. That is where the problem began. No matter what exception you throw in the class the Validation Summary never displays the error message. This behavior is by design and is intended to hide sensitive exception details from the end user. In this specific, however, I wanted to reveal the exception message to the end user because all exceptions were basically validation errors and I was sure that they are not disclosing any sensitive system information. To overcome the problem I developed a custom action filter. The remainder of this article explains how the custom action filter works.

To understand the problem let's first reproduce the error by developing a sample LINQ to SQL class. Begin by creating a new ASP.NET MVC web application. Once created add a SQL database to it and create an Employee table in it. The schema of the Employee table is shown below:

image

Now add a new LINQ to SQL class to the web application and create the Employee LINQ to SQL class by dragging and dropping the Employees table from the Server Explorer onto the design surface of .dbml file.

image

Now, add a new class in the Models folder and modify it as shown below:

public partial class Employee
{
  partial void OnFirstNameChanging(string value)
  {
    if (value.Length < 3 || value.Length > 50)
    {
      throw new ValidationException("Invalid First Name.");
    }
  }
  partial void OnLastNameChanging(string value)
  {
    if (value.Length < 3 || value.Length > 50)
    {
      throw new ValidationException("Invalid Last Name.");
    }
  }
  ...
}

The code creates a partial class Employee and adds extensibility methods OnFirstNameChanging() and OnLastNameChanging() to it (you can add additional methods for other properties if you so wish). If there are any validation errors the methods simply throw ValidationException.

Add a controller named HomeController as shown below:

public class HomeController : Controller
{
    public ActionResult Index()
    {
        return View();
    }

    [HttpPost]
    public ActionResult Index(Employee emp)
    {
        return View();
    }

}

The Index() action method simply renders the Index view. The Index view consists of three textboxes to enter FirstName, LastName and BirthDate and uses MVC Validations (see markup and screen capture below).

<% using (Html.BeginForm()) { %>
<%= Html.ValidationSummary()%>
<p>First Name :</p>
<%= Html.TextBoxFor(model => model.FirstName)%>
<%= Html.ValidationMessageFor(model => model.FirstName, "*")%>
<p>Last Name :</p>
<%= Html.TextBoxFor(model => model.LastName)%>
<%= Html.ValidationMessageFor(model => model.LastName, "*")%>
<p>Birth Date :</p>
<%= Html.TextBoxFor(model => model.BirthDate)%>
<%= Html.ValidationMessageFor(model => model.BirthDate, "*")%>
<p>
<input id="Submit1" type="submit" value="Submit" />
</p>
<%}%>

image

If you try entering invalid values in FirstName and LastName fields you will get errors as shown below:

image

Notice that though FirstName and LastName fields are showing error (*) there is no descriptive error message at all. The actual error messages ("Invalid First Name" and "Invalid Last Name") are suppressed by MVC framework for security reasons. However, since you are deliberately throwing these exceptions you know that showing them to the end user won't create any problem.

The solution is to create a custom action filter as shown below:

public class ShowExceptionDetailsInValidationSummary : FilterAttribute, IActionFilter
{
    public Type ExceptionType { get; set; }
    public string Keys { get; set; }
    public string ErrorMessage { get; set; }

    public void OnActionExecuting(ActionExecutingContext filterContext)
    {
        Controller c = (Controller)filterContext.Controller;
        string[] keys = null;
        Dictionary<string, string> messages = new Dictionary<string, string>();

        if (Keys != null && Keys!=string.Empty)
        {
            keys=Keys.Split(',');
        }

        if (keys == null)
        {
            foreach (string key in c.ModelState.Keys)
            {
                foreach (ModelError err in c.ModelState[key].Errors)
                {
                    if (ExceptionType != null)
                    {
                        if (err.Exception.GetType().Equals(ExceptionType))
                        {
                            if (ErrorMessage == null || ErrorMessage == string.Empty)
                            {
                                messages.Add(key, err.Exception.Message);
                            }
                            else
                            {
                                messages.Add(key, ErrorMessage);
                            }
                        }
                    }
                    else
                    {
                        if (ErrorMessage == null || ErrorMessage == string.Empty)
                        {
                            messages.Add(key, err.Exception.Message);
                        }
                        else
                        {
                            messages.Add(key, ErrorMessage);
                        }
                    }
                }
            }
        }
        else
        {
            foreach (string key in keys)
            {
                if(c.ModelState.Keys.Contains(key))
                {
                    foreach (ModelError err in c.ModelState[key].Errors)
                    {
                        if (ExceptionType != null)
                        {
                            if (err.Exception.GetType().Equals(ExceptionType))
                            {
                                if (ErrorMessage == null || ErrorMessage == string.Empty)
                                {
                                    messages.Add(key, err.Exception.Message);
                                }
                                else
                                {
                                    messages.Add(key, ErrorMessage);
                                }
                            }
                        }
                        else
                        {
                            if (ErrorMessage == null || ErrorMessage == string.Empty)
                            {
                                messages.Add(key, err.Exception.Message);
                            }
                            else
                            {
                                messages.Add(key, ErrorMessage);
                            }
                        }
                    }
                }
            }
        }

        foreach (string key in messages.Keys)
        {
            c.ModelState.AddModelError(key, messages[key]);
        }
    }

    public void OnActionExecuted(ActionExecutedContext filterContext)
    {
    }

}

The ShowExceptionDetailsInValidationSummary action filter essentially loops through the Model Errors and then programmatically adds a model error message.

Once the ShowExceptionDetailsInValidationSummary action filter is ready you can decorate the Index() action method with it as shown below :

[HttpPost]
[ShowExceptionDetailsInValidationSummary]
public ActionResult Index(Employee emp)
{
    return View();
}

If you run the application again and try to enter invalid values for FirstName and LastName you will correctly get error messages as shown in the following figure.

image

By default ShowExceptionDetailsInValidationSummary action filter will display all the exception messages in the validation summary. You can also specify that only certain exceptions be displayed.

[ShowExceptionDetailsInValidationSummary(ExceptionType=typeof(ValidationException))]

Further you can also customize the error message and keys using ErrorMessage and Keys properties of ShowExceptionDetailsInValidationSummary class respectively.

READ MORE

Displaying images from wellknown URLs is quite straightforward. At times, however, you need to display images that are available as raw binary data. Consider, for example, that you are building a Captcha system that generates images on the fly. These images won't reside on the server as physical files. They will be generated and held in memory using System.Drawing classes (or something similar). To display them you can't point the src attribute of an <img> element to a particular URL as such.

There can be multiple ways to deal with this problem. This article discusses a couple of them.

The first approach that I discuss involves sending a Base64 representation of the image through ViewBag. The action method under consideration generates such a Base64 version of the image (often called Data URL) and then pass it to the view via a ViewBag property. Here is how this is done:

public ActionResult Index()
{
    string path = Server.MapPath("~/images/computer.png");
    byte[] imageByteData = System.IO.File.ReadAllBytes(path);
    string imageBase64Data=Convert.ToBase64String(imageByteData);
    string imageDataURL= string.Format("data:image/png;base64,{0}", imageBase64Data);
    ViewBag.ImageData = imageDataURL;
    return View();
}

The above code shows Index() action method of HomeController. For the sake of simplicity it uses a physical image file rather than dynamically generated image. The image file is read as a byte array using ReadAllBytes() method. In a more realistic situation you will replace the first two lines with the image generation logic of your own.

Once the image content is read as a byte array, it is converted into a Base64 string using ToBase64String() method of Convert class. This Base64 string is used to form a data URL as shown. Notice how the data URL has data:image/png;base64 at the beginning. This way the browser knows that the src attribute value itself contains the image data. Make sure to change the image type (.png / .jpg / .gif etc.) as per your needs, Then a ViewBag variable named ImageData is set to this data URL.

The Index view makes use of this ViewBag property as shown below:

<img src="@ViewBag.ImageData" />

The following figure shows a sample run of the above code:

image

Let's see another technique to achieve the same result. This technique calls for creation of another action method. Instead of passing image data through a ViewBag property the src attribute of the <img> element will point to the second action method you create. Here is how this approach works:

public ActionResult GetImage()
{
    string path = Server.MapPath("~/images/computer.png");
    byte[] imageByteData = System.IO.File.ReadAllBytes(path);
    return File(imageByteData, "image/png"); 
}

Here, the GetImage() action method reads the image file into a byte array. It then uses File() method of the Controller base class to send the contents to the caller. The first parameter is a byte array that represents the file content and the second parameter indicates the MIME content type. Make sure to change the content type as per your needs.

To use the GetImage() action method you will write this markup in the view:

<img src='@Url.Action("GetImage", "Home")'/>

The src attribute of the image tag points to /Home/GetImage. If you run the application the result would be the same as in earlier case.

That's it! Keep coding!!

READ MORE
...