I currently use an ajax post back to an action to create a new database object and then return the rendered html for a view of that object. I then inject that html into the page.
I was wondering, since MVC does easily allow you to render views to HTML from within controllers, if there was a better (or more correct) approach to the problem.
(I currently use this code to render the view as html in the action).
Any and all ideas appreciated.
As a matter of fact there is. Just return a partial view.
public ActionResult AjaxStuff()
{
// do whatever
var model = ...;
return PartialView(model);
}
It's true you can render it using PartialView or doing it custom via JSON (PartialView is just so much easier!).
It really depends on your implementation and choices regarding graceful degradation.
What I usually do is:
[HttpGet]
public ActionResult SignIn()
{
//Check if it is an AJAX request
if (Request.IsAjaxRequest())
return PartialView();
else
return View();
}
So it is possible to get your cake and eat it too.
Truth be told there are various ways to get this view from the server. You could use the asp.net mvc ajax js library, use .getJSON or .ajax, or even do it via JQuery using .load: http://api.jquery.com/load/
You can render a PartialView as the other answer suggested or return JsonResult and populate your HTML from the JSON data. Your call.
Related
From what I read in various tutorials, or simply the sample project, the Controller use the ViewBag to store anything that will be dipsplayed on the page.
In the Controller I can just type ViewBag.AnythingIWant = "Foo";, and it'll render in the HTML. Apparently that's what is done in the sample project, at least for title and various texts.
But most of the text is hardcoded in the HTML and obviously I don't want that. Considering I'm not new to C# or MVC in Xamarin (mobile development), I feel like I should grasp this pretty quick, but I don't. Could someone clarify to me the following :
My Controller knows the ViewModel (which does most of the work) and himself uses the Model privately. I'm used (from iOS dev) for the controller to be the last layer of UI, and inside the controller I would just have all my labels and whatever I want, and I can fill them with whatever is available in the ViewModel.
Here, there is this HTML layer that I don't know how to connect to the controller.
I have a strong feeling that putting everything in the ViewBag cannot be the way to go. Could anyone briefly reveal the piece I am missing to use proper objects inside the HTML ?
Razor might be what's confusing me, considering whatever I add publicly in my Controller, I can't find it in the related HTML using #MyProperty
I know this is pretty broad question but I know that I only miss a small piece of knowledge to unlock everything.
As noted in the comments, pass a ViewModel to the View to be rendered!
Controller
public ActionResult Index() {
var viewModel = new MyViewModel { Name = "some string" };
return View("Index", viewModel);
}
Index.cshtml
#model MyViewModel #* Tell Razor which ViewModel to expect *#
#{ string name = Model.Name; }
What i'm trying to do:
call an UmbracoApiController method from js
do some server side processing
return a partial view with a custom model as a string
I've found helpful answers like:
http://our.umbraco.org/wiki/reference/code-snippets/razor-snippets/render-razor-scripts-for-emails-and-more/create-string-of-partial-view-for-emailing
http://our.umbraco.org/forum/developers/api-questions/44916-615-Returning-Partial-View-as-string-to-JSON
But I can't figure out what Controller or ControllerContext to pass in since I'm not in a Razor view or RenderMvcController.
Some help please?
I normally call (with js, ajax, jquery) an Umbraco Surface controller and I pass my current page's node id. Then on server, I do:
var currentPage = Umbraco.TypedContent(myNodeIdParam);
From there I usually have enough context and I can return a partial view, passing it a model I want.
Hope this helps!
I am trying to overload an MVC Action, but since "overloading" does not work for route actions (error 500 says ambiguous method I guess because parameters cannot be strongly typed from the browser), then I thought I would just return one action to another since I cannot use RedirectToAction for HttpPost either. The issue is that it is trying to find a view with the new action name instead of what aciton I am trying to call. Here is what I am trying to do:
[HttpPost]
public ActionResult DetailForProductID(int productID)
{
return Detail(new[] { GetProductById(productID) });
}
[HttpPost]
public ActionResult Detail(IEnumerable<Product> products)
{
....
return View(productViewModel);
}
This is the error I get though:
The view 'DetailForProductID' or its master was not found or no view engine supports the searched locations. The following locations were searched:
~/Views/Products/DetailForProductID.aspx
~/Views/Products/DetailForProductID.ascx
~/Views/Shared/DetailForProductID.aspx
~/Views/Shared/DetailForProductID.ascx
~/Views/Products/DetailForProductID.cshtml
~/Views/Products/DetailForProductID.vbhtml
~/Views/Shared/DetailForProductID.cshtml
~/Views/Shared/DetailForProductID.vbhtml
What is the most robust and elegant way to handle this? I would not want to store things in temporary sessions or do a RedirectToAction becase I should be able to do everything server-side. Any help or advice would be greatly appreciated.
For redirecting like that, I would recommend the following:
return RedirectToAction("Detail", data);
But I am not sure why you have the need for this. Looking at your actions, why not do it like this instead?
public ActionResult Detail(int productId)
{
var data = GetProductById(productID);
....
return View(productViewModel);
}
You can also use Tempdata in this scenario, for example:
public ActionResult DetailForProductID(int productID)
{
IEnumerable<Product> data = GetProductById(productID);
TempData["ProductData"]= data;
return RedirectToAction("Detail",data);
}
public ActionResult Detail(IEnumerable<Product> products)
{
....
if(TempData["ProductData"]!=null){
IEnumerable<Product> data = (IEnumerable<Product>)TempData["ProductData"];
return View(data);
}else {
return View(products);
}
}
I believe it is important to keep controllers "thin" and "dumb". Once you get beyond a simple website and need to build something more complex, you don't want to have to rewrite the same code multiple times (not good for many reasons). It will also become impossible to use the controllers as generic functions (which is basically what you are attempting to do now).
A more elegant and robust way to handle this would be to abstract away your application logic and perform it somewhere else. Then you could call pieces of the logic depending on the action's requirements. To start moving in that direction, you could write the controller specific logic in each controller, then determine what function is shared between both of them and place that somewhere else in your project. In more complex projects it is not unusual to have no application logic in the controller at all.
You might want to try to create one generic function that returns a "Product" probably by Id and place that function somewhere else. Then use the controller to determine the specific logic and call the shared function to get the product by Id.
I have certain situations where I need to pass a value between controller actions.
When passing a returnUrl from a view to all nested views. In the
view I have
#{
TempData["returnURL"] = Request.Url.AbsoluteUri;
}
and then access it in a similar way to this (in my real version I
check that the key is in TempData and that the returnURL is a real
URL):
return Redirect(TempData["returnURL"].ToString());
If it needs to continue on past the first page change (i.e. Search
page -> Edit page -> Edit Section page) I'm adding it again
TempData["returnURL"] = TempData["returnURL"];
When I need to pass a value from one controller action through a
view to another controller action that is called by ajax such as
here:
public ViewResult Index(FormCollection form)
{
var model = new GridColumnChooserViewModel();
//Select deleted/not deleted rows
if (form.HasKeys())
model.ShowRows = (form["deletedDropDown"] == null) ?
"Active" :
GetOptionByName(form["deletedDropDown"]);
TempData["ShowRows"] = model.ShowRows;
...
}
and then in my other ajax-called action controller I access it:
public JsonResult GetData()
{
//Select deleted/not deleted rows
var showRows = (TempData.ContainsKey("ShowRows") && TempData["ShowRows"] == null) ?
"Active" :
GetOptionByName(TempData["ShowRows"].ToString());
//refresh tempdata showrows so it is there for next call
TempData["ShowRows"] = model.ShowRows;
return this.GetDataSource(showRows);
}
My question is, is this really bad practice? From my understanding of it, I'm essentially using TempData like a session cookie. Is there a better way to do this, like using an actual cookie?
Yes, I would say that this in general is bad practice. While the ViewData dictionary approach is fast and fairly easy to implement it can leads to typo's and errors that are not caught at compile time. An alternative would be to use the ViewModel pattern which allows you to use strongly-typed classes for the specific view you need to expose values or content within. Ultimately giving you type safe and compile time checking along with intellisense.
My first choice would be to use a view model. If that doesn't fit then using session state may be just fine.
It seems like you are using TempData to flow state through the various pages of your site; in general, I'd say this is a bad practice.
Ideally, you would flow whatever upcoming state you would need to the client, and the client would store it (in some sort of JSON or whatever). Then, the client would return it to you as part of their action, and then you'd pass back the appropriate state, etc.; it speaks more to the stateless nature of HTTP applications.
I changed both situations to use Session so that I didn't have to keep pushing the TempData value on.
public ActionResult Create()
{
Session["returnURL"] = Request.UrlReferrer.AbsoluteUri;
...
}
Then I access it like this
var returnURL = (Session["returnURL"] != null) ? Session["returnURL"].ToString()
: Url.Action("Index", "Home");
Seems a bit better.
I am newish to MVC and understand all the great things about it, including the reasons why viewstate isn't available, however there are some circumstances where I think having some kind of view state will be quite handy, In my case I am thinking about list pages with various search filters that can be applied to the list.
Would it be worthwhile implementing some kind of pseudo viewstate to hold this info in some cases? or is there a better solution?
Any examples out there?
Your comments appreciated.
In ASP.NET MVC, state-retention is typically handled by round-tripping the state information back to the view.
For example, in the NerdDinner CRUD examples, they show how you can submit a form, check for errors, and if there are errors, display the form again with the data still intact, including the necessary error messages.
This works because the controller method handling the POST simply passes the data back to the view:
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Edit(int id, FormCollection formValues) {
Dinner dinner = dinnerRepository.GetDinner(id);
try {
UpdateModel(dinner);
dinnerRepository.Save();
// No Errors, return to detail view
return RedirectToAction("Details", new { id=dinner.DinnerID });
}
catch {
foreach (var issue in dinner.GetRuleViolations()) {
ModelState.AddModelError(issue.PropertyName, issue.ErrorMessage);
}
// Errors, display form again. Pass state information back to form.
return View(dinner);
}
}
The theoretical answer
ViewState is one of the concepts of ASP.NET WebForms which is considered VERY harmful. It is used to store the state of some controls and renders a very-VERY ugly hidden field into the HTML.
This is undesirable for some people for SEO (search engine optimization) and other reasons.
MVC doesn't provide some of the abstractions that WebForms does, for some very basic reasons:
It gives you full control over your URLs and output HTML
WebForms controls (especially the "advanced" ones) render junk HTML, MVC lets you write your own HTML
In order to avoid these faults, MVC doesn't use the principle of controls, events, and such stuff
If you wanted your web framework to abstract away the REAL way HTTP behaves, you should continue to use WebForms
In truth, HTTP is a stateless protocol, which WebForms try to hide from you by introducing the concept of Controls, and their state and events.
MVC doesn't lie to you and doesn't try to hide anything from you.
The practical anwser
You can use ViewData instead of ViewState.
You set an item (ViewData["Something"] = yourObject) in the Controller, and then you can retrieve it in the View. You can use it to "remember" whatever you want it to.
So, basically, persisting the information consists of reading it from Request.QueryString or Request.Form in the appropriate Controller action, setting it into the ViewData, and then retrieving the ViewData information in the View.
For example:
Controller action:
string textBoxValue = Request.Form["myTextBox"];
...
ViewData["myTextBox"] = textBoxValue;
View:
<% using (Html.BeginForm()) { %>
<%= Html.TextBox("myTextBox") %>
<% } %>
More stuff
Read some MVC-related questions here (such as this one), and read the MVC book (at least the free NerdDinner chapter).
MVC will be much more understandable for you then, I promise!