I have an PartialViewResult action which renders a PartialView that I call from a $.ajax call on the page.
That PartialView also has a foreach loop for the items in the VM and within that PartialView I have two RenderAction that render two other Partials.
It all works fine, except for the speed at which it's rendered. When I comment out the two nested RenderAction, the main partial view renders extremely fast. When I uncomment them, the main partial view renders between 3 to 5 seconds. Even if I remove all data from the partial views and all data from the actions to only return an empty view, it still takes 3-5 seconds.
Somehow, my app is having problems rendering these two partials even when they're empty.
My code:
Main action:
public PartialViewResult MyTasks(int milestoneId, int currentPage = 1)
{
var mergedTasks = new List<MergedTask>();
var TrackingTeams = _TrackingTeams.GetAll().ToList();
var pagingInfo = new PagingInfo() {CurrentPage = currentPage, ItemsPerPage = 10, TotalItems = _TrackingTeams.GetAll().Count() };
mergedTasks.AddRange(from TrackingTeam in TrackingTeams
let task = allTasks.Single(x=>x.TestId == (int)TrackingTeam.TrackingTask.TestId)
select new MergedTask()
{
Summary = TrackingTeam.TrackingTask.Description,
InternalId = task.Id,
DevTrackingTask = TrackingTeam.TrackingTask,
LastUpdate = task.DateModified
});
return PartialView(new DevTrackingTaskViewModel
{
MergedTasks = mergedTasks,
Category = _categories.GetById(categoryId),
PagingInfo = pagingInfo
});
}
The ViewModel associated with it:
public class TrackingTaskViewModel
{
public List<MergedTask> MergedTasks { get; set; }
public int CountTasks { get; set; }
public PagingInfo PagingInfo { get; set; }
public Category Category { get; set; }
}
public class MergedTask
{
public int InternalId { get; set; }
public string Summary { get; set; }
public TrackingTask TrackingTask { get; set; }
public DateTime LastUpdate { get; set; }
}
My main PartialView:
#foreach (var item in Model.MergedTasks)
{
<script type="text/javascript">
$(document).ready(function () {
$("#TrackingTask#(item.TrackingTask.Id)").hover(function () {
if ($("#snapshotFixerForTrackTask#(item.TrackingTask.Id)").length == 1) {
$("#expandTrackingTaskForTask#(item.TrackingTask.Id)").removeClass("hide");
}
else {
$("#expandTrackingTaskForTask#(item.TrackingTask.Id)").toggleClass("hide");
}
});
});
</script>
<div class="TrackingTaskDiv" id="TrackingTask#(item.TrackingTask.Id)">
<div class="TrackingContainer">
<div id="flagsForTrackingTask#(item.TrackingTask.Id)" class="flags">
#{Html.RenderAction("ShowFlags", "Task", new { trackingid = item.TrackingTask.Id });}
</div>
<div id="TestStatusForTrackTask#(item.TrackingTask.Id)" class="TestStatusWrapper">
#{Html.RenderAction("CheckTrackStatus", "Task", new { trackingid = item.TrackingTask.Id });}
</div>
</div>
<div id="expandTrackingTaskForTask#(item.TrackingTask.Id)" class="expandTrackingTask collapsed hide"></div>
</div>
}
I can paste in the Action for the "ShowFlags" and "CheckTrackStatus" if needed. But as I mentionned even if I remove all the code from the action and the view the rendering still takes 3-5 seconds to render the whole view, there's no difference.
One solution we came up with would be to remove the partials altogether, put the VM of each partial inside the main VM and do the same for the HTML in the partials. But I like the idea of compartmentalizing specific features on a view.
LanFeusT (great name!!),
RenderAction will cause a performance overhead as it makes a complete MVC cycle, rather than just using the current controller context. you may be advised to seek an alternative approach (such as including the required elements in your viewModel). i found this out the hard way as well and it was only when profiling my code that i appreciated the amount of reflection going on for each new RenderAction call (which in 99% of circumstances is both convenient and appropriate).
So my bottom line advice - look at extending your viewmodel.
I urge you to google RenderAction vs RenderPartial for further info..
see:
RenderAction RenderPartial
Related
I've searched all the available tutorials I can find, and I'm still having trouble with Umbraco Surface Controllers. I've created a bare-bones Surface Controller example which sorta works, but has some issues. Here's my code so far, questions to follow:
ContactformModel1.cs:
public class ContactFormModel1
{
public string Email { get; set; }
public string Name { get; set; }
public string HoneyPot { get; set; }
public string Title { get; set; }
public string Last { get; set; }
public string First { get; set; }
public string Addr { get; set; }
public string Phone { get; set; }
public string Time { get; set; }
public string Comment { get; set; }
}
ContactSurfaceController.cs:
public class ContactSurfaceController : Umbraco.Web.Mvc.SurfaceController
{
public ActionResult Index()
{
return Content("this is some test content...");
}
[HttpGet]
[ActionName("ContactForm")]
public ActionResult ContactFormGet(ContactFormModel1 model)
{
return PartialView("~/Views/ContactSurface/Contact1.cshtml", model);
}
[HttpPost]
[ActionName("ContactForm")]
public ActionResult ContactFormPost(ContactFormModel1 model)
{
// Return the form, just append some exclamation points to the email address
model.Email += "!!!!";
return ContactFormGet(model);
}
public ActionResult SayOK(ContactFormModel1 model)
{
return Content("OK");
}
}
Contact.cshtml:
#model ContactFormModel1
#using (Html.BeginUmbracoForm<ContactSurfaceController>("ContactForm"))
{
#Html.EditorFor(x => Model)
<input type="submit" />
}
ContactMacroPartial.cshtml:
#inherits Umbraco.Web.Macros.PartialViewMacroPage
#Html.Action("ContactForm", "ContactSurface")
My Questions:
I'm pretty sure that return ContactFormGet(model) is wrong in the
ContactFormPost method, but everything else I've tried throws an error.
When I try return RedirectToCurrentUmbracoPage(), I get Cannot
find the Umbraco route definition in the route values, the request
must be made in the context of an Umbraco request.
When I try return CurrentUmbracoPage(), I get Can only use
UmbracoPageResult in the context of an Http POST when using a
SurfaceController form.
The routing appears to work correctly (when I put a breakpoint inside ContactFormPost, the debugger stops there). But when the form comes back, I get the exact values I submitted. I don't see the !!! appended to the email address. (Note, this bit of code is just for debugging, it's not meant to do anything useful).
How do I call the "SayOK" method in the controller? When I change the BeginUmbracoForm method to point to SayOK, I still get stuck in the ContactFormPost method.
I'm sure I'm missing something incredibly stupid, but I can't figure this out for the life of me.
I wanted to take a moment to say how I resolved this. After playing around some more, I realized that I didn't really state my problem clearly. Basically, all I'm trying to do is embed an MVC form inside a Partial View Macro, so that it could be used in the content of a page (not embedded in the template).
I could get this solution to work, but I really didn't like how much logic the author put inside the View file. So I adapted his solution this way:
Partial View Macro (cshtml) file:
#inherits Umbraco.Web.Macros.PartialViewMacroPage
#using Intrepiware.Models
#{
bool isPostback = !String.IsNullOrEmpty(Request.Form["submit-button"]);
if(isPostback)
{
#Html.Action("CreateComment", "ContactSurface", Request.Form)
}
else
{
#Html.Partial("~/Views/Partials/ContactForm.cshtml", new ContactFormModel())
}
}
Form Partial View (cshtml) file:
#using Intrepiware.Models
#using Intrepiware.Controllers
#model ContactFormModel
<p>
<span style="color: red;">#TempData["Errors"]</span>
</p>
<p>
#TempData["Success"]
</p>
<div id="cp_contact_form">
#using(Html.BeginUmbracoForm("CreateComment", "BlogPostSurface"))
{
#* Form code goes here *#
}
ContactSurfaceController.cs file:
public class ContactSurfaceController : Umbraco.Web.Mvc.SurfaceController
{
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult ubCreateComment(ContactFormModel model)
{
if (processComment(model) == false)
return CurrentUmbracoPage();
else
return RedirectToCurrentUmbracoPage();
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult CreateComment(ContactFormModel model)
{
if(processComment(model) == true)
{
TempData["Success"] = "Thank you for your interest. We will be in contact with you shortly.";
ModelState.Clear();
}
return PartialView("~/Views/Partials/ContactForm.cshtml");
}
private bool processComment(ContactFormModel model)
{
// Handle the model validation and processing; return true if success
}
}
The controller is designed so that the form can be embedded either in the template or a Partial View Macro. If it's embedded in a template, the form should post to ubCreateComment; if it's in a macro, post to CreateComment.
I'm almost positive there's a better/more correct way of doing this, but I ran out of time to work on the project. If someone has a better solution, please post it!
One final question/note: You'll notice that the partial view macro posts Request.Form to the ContactSurfaceController.CreateComment, and MVC magically serializes it for me. That's safe, yeah? If so, doesn't MVC rock? :)
You are using a ChildAction because you are specifying #Html.Action("ContactForm", "ContactSurface") and because of this, in your View you need to:
Use Html.BeginForm(...) and not 'Html.BeginUmbracoForm(...)'
Allow the form to post back to the same path and not to the action
If you do this, then the form will post back to itself as expected.
See the documentation here for further help.
Edit:
Just saw the final part to your question. If you intend SayOK to be your 'thank you' message, I would just call it from your HttpPost action instead of returning the initial view.
I followed Darin's post at
multi-step registration process issues in asp.net mvc (splitted viewmodels, single model)
Its a very elegant solution, however im having trouble seeing how you would populate the individual step viewmodels with data. Im trying to emulate amazons checkout step-system which starts with selecting an address, then shipping options, then payment information.
For my first viewmodel i require a list of addresses for my current logged in user which i poll the database for to display on screen
In my head, this is the viewmodel that makes sense to me.
[Serializable]
public class ShippingAddressViewModel : IStepViewModel
{
public List<AddressViewModel> Addresses { get; set; }
[Required(ErrorMessage="You must select a shipping address")]
public Int32? SelectedAddressId { get; set; }
#region IStepViewModel Members
private const Int32 stepNumber = 1;
public int GetStepNumber()
{
return stepNumber;
}
#endregion
}
However there seems to be no good way to populate the addresses from the controller.
public class WizardController : Controller
{
public ActionResult Index()
{
var wizard = new WizardViewModel();
wizard.Initialize();
return View(wizard);
}
[HttpPost]
public ActionResult Index(
[Deserialize] WizardViewModel wizard,
IStepViewModel step)
{
wizard.Steps[wizard.CurrentStepIndex] = step;
if (ModelState.IsValid)
{
if (!string.IsNullOrEmpty(Request["next"]))
{
wizard.CurrentStepIndex++;
}
else if (!string.IsNullOrEmpty(Request["prev"]))
{
wizard.CurrentStepIndex--;
}
else
{
// TODO: we have finished: all the step partial
// view models have passed validation => map them
// back to the domain model and do some processing with
// the results
return Content("thanks for filling this form", "text/plain");
}
}
else if (!string.IsNullOrEmpty(Request["prev"]))
{
// Even if validation failed we allow the user to
// navigate to previous steps
wizard.CurrentStepIndex--;
}
return View(wizard);
}
}
So i removed the list of address view models
[Serializable]
public class ShippingAddressViewModel : IStepViewModel
{
[Required(ErrorMessage="You must select a shipping address")]
public Int32? SelectedAddressId { get; set; }
#region IStepViewModel Members
private const Int32 stepNumber = 1;
public int GetStepNumber()
{
return stepNumber;
}
#endregion
}
This is what i came up with a custom editor template for the view model. It calls a Html.RenderAction which returns a partial view from my user controller of all the addresses and uses Jquery to populate a hidden input field for the view model's required SelectedAddressId property.
#model ViewModels.Checkout.ShippingAddressViewModel
<script src="../../Scripts/jquery-1.7.1.js" type="text/javascript"></script>
<script type="text/javascript">
$(document).ready(function () {
//Check to see if the shipping id is already set
var shippingID = $("#SelectedAddressId").val();
if (shippingID != null) {
$("#address-id-" + shippingID.toString()).addClass("selected");
}
$(".address-id-link").click(function () {
var shipAddrId = $(this).attr("data-addressid").valueOf();
$("#SelectedAddressId").val(shipAddrId);
$(this).parent("", $("li")).addClass("selected").siblings().removeClass("selected");
});
});
</script>
<div>
#Html.ValidationMessageFor(m => m.SelectedAddressId)
#Html.HiddenFor(s => s.SelectedAddressId)
<div id="ship-to-container">
#{Html.RenderAction("UserAddresses", "User", null);}
</div>
</div>
And the users controller action
[ChildActionOnly]
public ActionResult UserAddresses()
{
var user = db.Users.Include("Addresses").FirstOrDefault(
u => u.UserID == WebSecurity.CurrentUserId);
if (user != null)
{
return PartialView("UserAddressesPartial",
Mapper.Map<List<AddressViewModel>>(user.Addresses));
}
return Content("An error occured");
}
The partial view
#model IEnumerable<AddressViewModel>
<ul>
#foreach (var item in Model)
{
<li id="address-id-#item.AddressID">
#Html.DisplayFor(c => item)
<a class="address-id-link" href="#" data-addressid="#item.AddressID">Ship To this Address
</a></li>
}
</ul>
My solution just seems super out of the way/sloppy to me, is there a better more concise way to populate the viewmodel than using partial views from a different controller for this?
There's nothing wrong with using a child action like this to populate the user's addresses. In fact, I think this is actually the optimal approach. You've got full separation of concerns and single responsibility in play. Just because something requires more "pieces" (extra action, views, etc.) doesn't make it sloppy or otherwise wrong.
The only other way to handle this would be with dependency injection. Namely, your ShippingAddressViewModel would need a dependency of the currently logged in user, so that it could populate the list of addresses from that in its constructor. However, since ShippingAddressViewModel is not exposed in your view, you would have to pass the dependency through Wizard which is a bit of code smell. Wizard is not dependent on a user, but it would have dependence forced upon it by virtue of having your view model abstracted away inside it.
Long and short, while there's a way you could do this without the child actions and partial views, it would actually be nastier and sloppier than with them.
I'm a desktop developer and I'm teaching myself ASP.NET MVC3 with C# (can't use MVC4 yet...). I'm slapping together some small dummy projects for this reason. In this project I use a DropDownListFor with movie categories (we all know this example right?). But because this tutorial was going too fast I'm trying to do something more simple.
My Model:
public class MovieModel {
public int SelectedCategorieID { get; set; }
public List<CategorieModel> Categories { get; set; }
public MovieModel() {
this.SelectedCategorieID = 0;
this.Categories = new List<CategorieModel>() {new CategorieModel {ID = 1,
Name = "Drama"},
new CategorieModel {ID = 2,
Name = "Scifi"}};
}
}
public class CategorieModel {
public int ID { get; set; }
public string Name { get; set; }
}
See? Very simple. I have a strongly typed View in which I can use this model:
#model MvcDropDownList.Models.MovieModel (1st line of Index.cshtml).
The model is filled when the default action of the Home controller is called:
public ActionResult Index() {
ViewBag.Message = "Welcome to ASP.NET MVC!";
Models.MovieModel mm = new Models.MovieModel();
return View(mm);
}
So far so good. No problems. Now I want to show the user the ID of the category it selected in a partial view with unobtrusive ajax... Because I didn't get it to work I started even smaller. Forget the DrowpdownList for now. All I have at the moment is this button:
<input type="button" value="Minicart test" onclick="categoryChosen();" />
And this div:
<div id="minicart">
#Html.Partial("Information")
</div>
The mini cart stuff is from another tutorial, I apologize. Don't let it distract you please.
This javascript:
function categoryChosen() {
var url = "Home/CategoryChosen/" + "2";
$.post(url, function (data) {
debugger;
$("#miniCart").html(data);
});
}
The 2 is indeed solid, from my earlier attempt to get it to work. Eventually I want that to be variable ofcourse...
Which calls this action:
[AcceptVerbs("POST")]
public ActionResult CategoryChosen(string SelectedCategorieID) {
ViewBag.messageString = "2";
return PartialView("Information");
}
Yup, and you see that correctly. I just insert 2 for my test. Because like I said, can't get it to work. The partial view Information looks like this:
#{
ViewBag.Title = "Information";
}
<h2>Information</h2>
<h2>You selected: #ViewBag.messageString</h2>
So, now for the big question. I expected the partial view to render: "You selected: 2". I even see this when I debug the javascript and look what's inside the variable 'data'. Can anyone help me why it doesn't render 2? Then I can move on with teaching myself this stuff. Thank you very much in advance for helping. If you miss any kind of information, do not hesitate to ask.
I think the problem is misspelling id of the minicart div. Your id do not contain any capital letters but your selector does. So instead $("#miniCart") you should use $("#minicart") and it will work.
Make it like this and check if it works
function categoryChosen() {
var url = "Home/CategoryChosen?SelectedCategorieID=" + "2";
$.post(url, function (data) {
debugger;
$("#miniCart").html(data);
});
}
This is provided considering that you haven't did any changes to your routers in global.asax
rather you should add the url like this
UrlHelper
Perhaps this has been addressed somewhere else, but I can't find the keywords to search for.
In ASP.NET MVC with the Razor view engine: I have a view which renders with the same if statement repeated numerous times, in different parts of the html. I'm wondering if there's a way to consolidate all of these if's into a single if and set values for placeholders that would save the spot where each of those if's are, like so:
<div>#ph1</div>
<div>#ph3</div>
<div>#ph2</div>
#if(true)
{
ph1 = "<div>adfad<div>"
ph2 = "dsfaadfad"
ph3 = Model.Value
}
This is kind of a stupid example, but I think it makes the point of what I mean.
Your code is fine. You could also use a Razor Helper
http://weblogs.asp.net/scottgu/archive/2011/05/12/asp-net-mvc-3-and-the-helper-syntax-within-razor.aspx
#{
MvcHtmlString ph1 = new MvcHtmlString("");
MvcHtmlString ph2 = new MvcHtmlString("");
if (true)
{
ph1 = new MvcHtmlString("<div>" + Model.Value + "<div>");
ph2 = new MvcHtmlString("<div>fsgfdgdfg<div>");
}
}
#ph1
#ph2
Again, silly usage, but it makes the point.
As was suggested in one of the answers, a nice addition to what I have is to assign a helper. This makes it easier to assign multiple statements without a lot of concatenation.
#helper helper()
{
<span>#Model.Value</span>
<div>dsfadsfasdfdfa</div>
}
#{
MvcHtmlString ph1 = new MvcHtmlString("");
MvcHtmlString ph2 = new MvcHtmlString("");
if (true)
{
ph1 = new MvcHtmlString("<div>" + Model.Value + "<div>");
ph2 = new MvcHtmlString(helper().ToHtmlString());
}
}
#ph1
#ph2
If anybody has better ideas, I'd still be interested.
Views shouldn't normally have logic in your views (especially a single helper that is creating html). A better option is to use partial views or display for templtates.
models/[controller]
public class SomeViewModel()
{
//[UIHint("PhoneNumber")]
//public string ph1 { get; set; }
//[UIHint("PhoneNumber")]
//public string ph1 { get; set; }
//[UIHint("PhoneNumber")]
//public string ph1 { get; set; }
//if these all represent phone numbers, it would be ideal to
[UIHint("PhoneNumbers")]
IEnumerable<string> PhoneNumbers { get; set; }
}
views/[controllers]
#model SomeViewModel
#Html.DisplayFor(m => m.PhoneNumbers);
view/shared/DisplayTemplates/PhoneNumbers.cshtml
#model IENumerable<string>
#foreach (string phoneNumber in Model)
{
<div>#phoneNumber</div>
}
I have the following scenario:
I have a strongly typed partial view that display info for a view model called PlaceVieWModel it is rendered inside a strongly typed view for MainPlaceViewModel.
Classes:
public class PlaceViewModel
{
public int PlaceID { get; set; }
public string Name { get; set; }
public string Address { get; set; }
public decimal Latitude { get; set; }
public decimal Longitude { get; set; }
public bool HasLiveMusic { get; set; }
}
public class MainPlaceViewModel
{
public string Name { get; set; }
public string Description { get; set; }
public List<PlaceViewModel> Places { get; set; }
public string WebPage { get; set; }
public string LogoRoute { get; set; }
public string LogoDetailCssClass { get; set; }
}
In the MainPlaceModelView view I generate an Ajax.ActionLink for each of the MainPlaceViewModel.Places that gets the info for that Place and displays it in the partial view, this is the code for that:
#foreach (var item in Model.Places)
{
#Ajax.ActionLink(item.Name, "", "", new AjaxOptions { Url = Url.Action("GetPlaceInfo", "MainPlaces", new { placeId = item.PlaceID }), HttpMethod = "GET", UpdateTargetId = "placeInfo", InsertionMode = InsertionMode.Replace, LoadingElementId = "loading" })
}
This is the action:
public PartialViewResult GetPlaceInfo(int placeID)
{
var place = _db.Places.SingleOrDefault(p => p.PlaceID == placeID);
PlaceViewModel placeViewModel = new PlaceViewModel();
placeViewModel.Address = place.Address;
placeViewModel.HasLiveMusic = place.HasLiveMusic;
placeViewModel.Latitude = place.Latitude;
placeViewModel.Longitude = place.Longitude;
placeViewModel.Name = place.Name;
placeViewModel.PlaceID = place.PlaceID;
return PartialView("_PlaceInfo", placeViewModel);
}
My problem is how should I initialize the #Html.Partial, if i do it like this:
<div id="loading" style="display:none;">
Loading...
</div>
<div id="palceInfo">
#Html.Partial("_PlaceInfo", Model.Places.FirstOrDefault())
</div>
The partial view always shows the first item no matter which Ajax.ActionLink is pressed.
Any idea on how can I initialize it so that it also works when any of the ActionLinks is pressed?
UPDATE: The problem was a typo on my target div, the id was "palceInfo" and I´m setting the action link to update a "placeInfo", I can´t believe I lost so much time because of this.
Assuming that GetPlaceInfo(int placeID) action works right (returns proper data), I do not see other errors besides possibly one.
UpdateTargetId on your action link is equal to "placeInfo". From msdn: UpdateTargetId Gets or sets the ID of the DOM element to update by using the response from the server.
You did not show your view in full, but I think that you are missing an element there with id="placeInfo". If this is the case, ajax simply does not have an element where it wants to put the result. Try placing your partial into a div with such id:
<div id="placeInfo">
#Html.Partial("_PlaceInfo", Model.Places.FirstOrDefault())
</div>
UPDATE:
#Html.Partial() runs only one time - when your page loads first time. If it gets you right data first time, you should not need to mess with it anymore. The div that contains that partial will get updated when you call your GetPlaceInfo method through an action link. So there are two places to check: your GetPlacesInfo action and the ActionLink.
First, check GetPlacesInfo. Put a break point on it, pass different ids to it and make sure you get right results.
Second, I do not use MS Ajax extensions (I do something different for my ajax calls), but try this:
#Ajax.ActionLink(item.Name, "GetPlaceInfo", "MainPlaces", new { placeId = item.PlaceID }, new AjaxOptions { UpdateTargetId = "placeInfo", InsertionMode = InsertionMode.Replace, LoadingElementId = "loading" })
Assuming "MainPlaces" is the controller name