Advanced flow in controller, how to create reusable controllers methods - c#

I've got problem how to design correctly flow for my controller witch will do some advanced things. I have to have multiple-step adding course in my site. It looks like this:
public class CoursesController : Controller {
[HttpGet]
public ActionResult Create() //1 step - User fill some basic infos and send back forms to Save method
{
return View(model.GetNewInstanceOfCourse());
}
[HttpPost]
public ActionResult Save(NewCourse newCourse) //2 step - Datas are stored in session
{
string Token = Guid.NewGuid().ToString("D");
Session.Add(Token, newCourse);
return RedirectToAction("Subjects", new { Token = Token });
}
[HttpGet]
public ActionResult Subjects(string Token) //2 step - Users fill which Subjects will be on the course, then send forms to Confirm method
{
return View(model.GetAvaliableSubjects(Token/*to place Token in View and let retrieve object from session*/);
}
[HttpPost]
public ActionResult Confirm(Subjects subjects) //3 step - Users filled all required datas and now i want to store complete datas in database
{
//(assume that Session[...] return Dictionaty<string, ... > instead of object
if(!Session["stored-courses-from-first-step"].ContainsKey(subjects.RetrievedFromViewToken)
{
return RedirectToAction("Create");
}
model.AddNewCourse(Session["stored-courses-from-first-step"][subjects.RetrievedFromViewToken], subjects);
return RedirectToAction("Index");
}
}
and it works perfectly... but i have to write adding new subjects for existing course so reuse step 2 in other part of my controller. I have to fill some different datas, then reuse adding subjects for this datas and then reuse function Confirm but instead of inserting some datas i want just update and insert some datas completed from user.
...
public AddNewSubject(int CourseId)
{
...
}
In course preview i have button "Add new subjects", this should bring me to AddNewSubject Method and i don't know what to do next. I can't do in this method something like that:
return RedirectToAction("Save", "Courses", new { newCourse = model.GetExistingCourseAnChangeItToNewCourseInstance(CourseId)})
i don't want to write specialized methods for this due to duplicating most of this code. I think it's possible to reorganize flow in my controller but i doesn't have good idea how to do that. Other problem is that i need to reuse method Confirm, one time it will insert some datas, other time it will update some datas. Maybe you will have some good tips for me.

For existing courses, where you will be passing the course ID, there's nothing stopping you overloading the action method:
[HttpPost]
public ActionResult Save(int CourseId) //2 step - Datas are stored in session
{
// Do whatever you need to do...
}
This way, you do not have to worry about doing anything clever when trying to reuse the step 2 code you posted. You simply have a separate method for dealing with existing courses.

The cleanest solution is remove the real logic from the reusable actions, and put it in private methods.
In the method you decide what to do given some input params or something like that.
So, in your action you end up just calling the helper method. This helper methods can be private in the same controller... or if the amount of logic is large you can create a helper class to host that logic.

Related

Passing data between controllers ASP.NET [duplicate]

I'm using ASP.NET MVC 4. I am trying to pass data from one controller to another controller. I'm not getting this right. I'm not sure if this is possible?
Here is my source action method where I want to pass the data from:
public class ServerController : Controller
{
[HttpPost]
public ActionResult ApplicationPoolsUpdate(ServiceViewModel viewModel)
{
XDocument updatedResultsDocument = myService.UpdateApplicationPools();
// Redirect to ApplicationPool controller and pass
// updatedResultsDocument to be used in UpdateConfirmation action method
}
}
I need to pass it to this action method in this controller:
public class ApplicationPoolController : Controller
{
public ActionResult UpdateConfirmation(XDocument xDocument)
{
// Will add implementation code
return View();
}
}
I have tried the following in the ApplicationPoolsUpdate action method but it doesn't work:
return RedirectToAction("UpdateConfirmation", "ApplicationPool", new { xDocument = updatedResultsDocument });
return RedirectToAction("UpdateConfirmation", new { controller = "ApplicationPool", xDocument = updatedResultsDocument });
How would I achieve this?
HTTP and redirects
Let's first recap how ASP.NET MVC works:
When an HTTP request comes in, it is matched against a set of routes. If a route matches the request, the controller action corresponding to the route will be invoked.
Before invoking the action method, ASP.NET MVC performs model binding. Model binding is the process of mapping the content of the HTTP request, which is basically just text, to the strongly typed arguments of your action method
Let's also remind ourselves what a redirect is:
An HTTP redirect is a response that the webserver can send to the client, telling the client to look for the requested content under a different URL. The new URL is contained in a Location header that the webserver returns to the client. In ASP.NET MVC, you do an HTTP redirect by returning a RedirectResult from an action.
Passing data
If you were just passing simple values like strings and/or integers, you could pass them as query parameters in the URL in the Location header. This is what would happen if you used something like
return RedirectToAction("ActionName", "Controller", new { arg = updatedResultsDocument });
as others have suggested
The reason that this will not work is that the XDocument is a potentially very complex object. There is no straightforward way for the ASP.NET MVC framework to serialize the document into something that will fit in a URL and then model bind from the URL value back to your XDocument action parameter.
In general, passing the document to the client in order for the client to pass it back to the server on the next request, is a very brittle procedure: it would require all sorts of serialisation and deserialisation and all sorts of things could go wrong. If the document is large, it might also be a substantial waste of bandwidth and might severely impact the performance of your application.
Instead, what you want to do is keep the document around on the server and pass an identifier back to the client. The client then passes the identifier along with the next request and the server retrieves the document using this identifier.
Storing data for retrieval on the next request
So, the question now becomes, where does the server store the document in the meantime? Well, that is for you to decide and the best choice will depend upon your particular scenario. If this document needs to be available in the long run, you may want to store it on disk or in a database. If it contains only transient information, keeping it in the webserver's memory, in the ASP.NET cache or the Session (or TempData, which is more or less the same as the Session in the end) may be the right solution. Either way, you store the document under a key that will allow you to retrieve the document later:
int documentId = _myDocumentRepository.Save(updatedResultsDocument);
and then you return that key to the client:
return RedirectToAction("UpdateConfirmation", "ApplicationPoolController ", new { id = documentId });
When you want to retrieve the document, you simply fetch it based on the key:
public ActionResult UpdateConfirmation(int id)
{
XDocument doc = _myDocumentRepository.GetById(id);
ConfirmationModel model = new ConfirmationModel(doc);
return View(model);
}
Have you tried using ASP.NET MVC TempData ?
ASP.NET MVC TempData dictionary is used to share data between controller actions. The value of TempData persists until it is read or until the current user’s session times out. Persisting data in TempData is useful in scenarios such as redirection, when values are needed beyond a single request.
The code would be something like this:
[HttpPost]
public ActionResult ApplicationPoolsUpdate(ServiceViewModel viewModel)
{
XDocument updatedResultsDocument = myService.UpdateApplicationPools();
TempData["doc"] = updatedResultsDocument;
return RedirectToAction("UpdateConfirmation");
}
And in the ApplicationPoolController:
public ActionResult UpdateConfirmation()
{
if (TempData["doc"] != null)
{
XDocument updatedResultsDocument = (XDocument) TempData["doc"];
...
return View();
}
}
Personally I don't like to use TempData, but I prefer to pass a strongly typed object as explained in Passing Information Between Controllers in ASP.Net-MVC.
You should always find a way to make it explicit and expected.
I prefer to use this instead of TempData
public class Home1Controller : Controller
{
[HttpPost]
public ActionResult CheckBox(string date)
{
return RedirectToAction("ActionName", "Home2", new { Date =date });
}
}
and another controller Action is
public class Home2Controller : Controller
{
[HttpPost]
Public ActionResult ActionName(string Date)
{
// do whatever with Date
return View();
}
}
it is too late but i hope to be helpful for any one in the future
If you need to pass data from one controller to another you must pass data by route values.Because both are different request.if you send data from one page to another then you have to user query string(same as route values).
But you can do one trick :
In your calling action call the called action as a simple method :
public class ServerController : Controller
{
[HttpPost]
public ActionResult ApplicationPoolsUpdate(ServiceViewModel viewModel)
{
XDocument updatedResultsDocument = myService.UpdateApplicationPools();
ApplicationPoolController pool=new ApplicationPoolController(); //make an object of ApplicationPoolController class.
return pool.UpdateConfirmation(updatedResultsDocument); // call the ActionMethod you want as a simple method and pass the model as an argument.
// Redirect to ApplicationPool controller and pass
// updatedResultsDocument to be used in UpdateConfirmation action method
}
}

One controller for different views MVC

I would like to know if it is best to use One controller for multiple views that load approximately the same data or use one controller for every view.
And if it is possible to use the controller like this :
[Route("api/[controller]")]
public class MultiplePagesController : Controller
{
[HttpGet]
public async Task<IActionResult> GetA()
{ ... return viewA }
[HttpGet]
public async Task<IActionResult> GetB()
{ ... return viewB }
}
And in my serviceViewA.ts :
getA() {
return this.http.get<InterfaceA>(`${this.baseUrl}/api/multiplepages/`);
}
My serviceViewB.ts :
getB() {
return this.http.get<InterfaceB>(`${this.baseUrl}/api/multiplepages/`);
}
Since let's say the viewA returns data of books and the viewB returns data of books and computers.
I am using MVC .NET CORE
It's in fact a great idea to use one controller (and method) if it does exactly the same thing, you reduce code this way, makes it easy to maintain and modify in just one place rather than all over the project. Think of it as a container for your functions. If you have a Book controller, you put all methods related to books there, same for User, you would put all user methods there.
As for your second question, that goes against polymorphism. Your function signatures are exactly the same, how will it be distinguished? You'll need to give it a different name or accept a parameter (different from the other function or different data type).
Worth noting that you can name your function differently to return views.
For example:
public async Task<IActionResult> GetViewA(){ // ... }
public async Task<IActionResult> GetViewB(){ // ... }
You'll call those views by entering: /Controller/GetViewB as your path.

How to share controller actions/view with other controllers

I would like to call the MainController.GetData() action from a few different URLs without a bunch of copied/pasted code. Is there a preferred way people tackle this? Seems like I might be able to do it with routing as well. I would just like to reuse the view and action since all that code would be the same if I made a version for the GetMyData() action.
**Example urls**
*/main/getdata
/other/getmydata
/diffferent/getothersdata?userid=3
public ActionResult MainController::GetData()
{
var data = GetData();
return View(collection);
}
public ActionResult OtherController::GetMyData()
{
var userId = GetCurrentUserId();
var data = GetData(userId);
return View("../main/getdata", collection);
}
Although controllers look like simple classes, their behavior inside the MVC framework is more specialized. You can't, or perhaps it's better to say you shouldn't, just call an action from one in an action for another, as if it's just any old method. Mostly, this is because controllers have context, and you have to instantiate them and set them up just right so that everything works. That's non-trivial to do inside of an action method and it's going to make your code ugly as hell.
You have two choices really. If you just want the result of the action, the best method is to utilize HttpClient and actually submit an HTTP request for it, just like any other request that would activate it.
However, based on your problem description, option two is probably more appropriate. You can create a base class and then inherit from that. For example, if you know two controllers are both going to need GetData, you can do something like:
public abstract class BaseController : Controller
{
protected IEnumerable<Data> QueryData(int? userId = null)
{
...
}
}
public class MainController : BaseController
{
public ActionResult GetData()
{
var data = QueryData();
return View(data);
}
}
public class OtherController : BaseController
{
public ActionResult GetMyData()
{
var userId = GetCurrentUserId();
var data = QueryData(userId);
return View(data);
}
}
In other words, you factor out the common functionality into something both the actions on both derived controllers can use.
This is all you needed for this scenario, but you can also implement entire actions on the base controller. For example, if you added a Foo action to BaseController, both MainController and OtherController would then responds to requests for Foo, without having to actually explicitly define that. You can then also override these actions if you need to, as well.

WebAPI - Using a POST to do a GET's job when passing array parameters. What could go wrong?

I am new to ASP.Net WebAPI and I'm trying to use the NorthWind database to practice.
Now, I have a ProductsController which contains a GetAllProducts and a GetAllProductsById actions.
On the client-side, I have a multiselect dropdown control which is been populated with the categories of products from the CategoriesController class.
I want to populate a second dropdown control (not a multiselect) with the Products from the Categories that was selected in the category dropdown list.
My GetAllProductsById controller looks like this:
public IHttpActionResult GetAllProductsById([FromUri]int[] id)
{
using (var ctx = new NorthwindEntities())
{
<some codes...>
}
return Ok(productList);
}
Calling this service from the client, the URL look like this: http://localhost:1234/api/Products/GetAllProductsById?id=1&id=2&id=3
This is looks good with few parameters but what if the User selects more categories (let's say 30 out of 40 categories)? This means the URL would be so long.
So, I decide to use a POST to do a GET's job by decorating my action with HttpPost:
[HttpPost]
public IHttpActionResult GetAllProductsById([FromBody]int[] id)
This allows me to send the id parameters from the Body of my request.
Now, I am wondering if this style is correct or if someone can point me to a much cleaner way of passing a long list of parameters from a client to my webservice.
Thanks in advance.
NB:
Using Elasticsearch is not an option at the as suggested in the link below:
HTTP GET with request body
I tried using a modal class but it also has the same effect
http://localhost:1234/api/Products/GetAllProductsById?input.id=1&input.id=2
You could do something a bit hacky: use HTTP headers.
First the client should add the ID list as a header to its HTTP request (C# example follows):
var webRequest = System.Net.WebRequest.Create(your_api_url);
webRequest.Headers.Add("X-Hidden-List", serialized_list_of_ids);
And then on the API side:
[HttpGet]
public IHttpActionResult GetAllProductsById()
{
string headerValue = Request.Headers.GetValues("X-Hidden-List").FirstOrDefault();
if (!string.IsNullOrEmpty(headerValue))
{
var results = DeserializeListAndFetch(headerValue);
return Ok(results);
}
else
{
var results = ReturnEverything();
return Ok(results);
// or if you don't want to return everything:
// return BadRequest("Oops!");
}
}
DeserializeListAndFetch(...) and ReturnEverything() would do the actual database querying.
You can have the array of Id in a model class and in the method parameters just accept type of that class. Like
Modal Class
Public class modal
{
Public int[] Ids {get;set;}
}
Your Contoller
public IHttpActionResult GetAllProductsById([FromBody] modal m)
{
//Some logic with m.Ids
}
Your JSON if you are using any UI to consume this API
{"Ids" :[1,2,3,4,5,6]}
Hope this Helps,

Different view for Ajax postback without duplicating controller

I have an "Add to Cart" button that if the browser supports JS + Ajax (and doesn't have it turned off) it POSTS using Ajax back to the site, however if they don't support it, or have it turned off it does the manual style POST.
What I am hoping to accomplish is two views - one when the user posts back using a regular POST and one when it comes from a AJAX POST. That way I can show an in-line message (partial) or a full screen.
I would prefer not having to duplicate the controller/action code twice, it just seems non-elegant.
Is there any recommended solutions or patterns for this type of issue?
John,
You can use the IsAjaxRequest method on the request to determine this. You would apply it to your scenario thusly:
public ActionResult AddToCart(YourCartViewmodel cartViewmodel)
{
if (ModelState.IsValid)
{
// do the standard/common db stuff here
if(Request.IsAjaxRequest())
{
return PartialView("myPartialView");
}
else
{
return View("standardView");
}
}
/* always return full 'standard' postback if model error */
return View(cartViewmodel);
}
altho not perhaps giving a complete solution, this should give you a good start...
You can have two different actions in your controller. One for regular post and one for AJAX.
public ActionResult AddToCart(Viewmodel vm)
{
if (ModelState.IsValid)
{
DoStuff(vm);
return View("ViewForRegularPost");
}
/* error */
return View(vm);
}
and
public ActionResult JsonAddToCart(Viewmodel vm)
{
if (ModelState.IsValid)
{
DoStuff(vm);
return View("ViewForJS");
}
/* error */
return View(vm);
}
Instead of repeating your controller code, have a separate method for actual controller code.
public void DoStuff(Viewmodel vm)
{
//TODO : Actual controller code goes here
}

Categories

Resources