Describing my scenario will be the best way to describe what I'm trying to achieve. I'm looking for a cleaner solution, if it exists at all.
We have content that would like to lock. I abstracted the unlocking models because we could have different types. It could be a Redirect or rendered partial view or something else they could come up in the future, so I decided to try returning an ActionResult.
public abstract class AContentUnlocker
{
public abstract ActionResult GetUnlockActionResult();
}
public class RedirectUnlocker : AContentUnlocker
{
public override ActionResult GetUnlockActionResult()
{
return new RedirectResult("http://www.url1.com?returnUrl=mywebsiteagain");
}
}
public class PartialViewUnlocker: AContentUnlocker
{
public override ActionResult GetUnlockActionResult()
{
PartialViewResult view = new PartialViewResult();
view.ViewName = "_PartialViewToUnlock";
return view;
}
}
My Content would be represented in a model with the proper Unlocking Mechanism
public class MyContent
{
public string Description { get; set; }
public AContentUnlocker ContentUnlocker { get; set; }
}
In my controller, I would simply return my desired Content with the proper unlocking mechanism set.
public ActionResult Index()
{
MyContent myContent = new MyContent() {
Description = "Content 1",
ContentUnlocker = new PartialViewUnlocker()
};
return View(myContent);
}
In my index View, I would then Execute the ActionResult.
#{
Model.ContentUnlocker.GetUnlockActionResult().ExecuteResult(this.ViewContext);
}
return View(myContent);
The Redirect ActionResult works fine.
My problem is that with the Partial View Action Results, given the execution cycle of MVC, the partial view is rendered before the controller view. So I'm getting something like:
<!-- html of the partial view rendered -->
<div> blah blah </div>
<!-- html of the parent view -->
<html>
<head></head>
<body> blah .... </body>
</html>
I'm not sure it's possible, but is there a way to execute my ActionResult the same way Html.RenderPartial would?
Related
My app has a main dashboard which is comprised of 8 different partial views; each backed by their own view model and in my controller I'm just calling
public ActionResult mainDashboard(){
return View()
}
to return the dashboard. My question is would it be recommended to create a dashboard view model that also contains references to the view models of the partial views? What's considered a recommended best practice in this situation?
Ohkk here is a good idea as well to use html.Action instead of html.partial
This would look more like this:
public ActionResult Dashboard()
{
return View();
}
public PartialViewResult Clubs()
{
....
return PartialView(db.Clubs.ToList());//this assumes the partial view is named Clubs.cshtml, otherwise you'll need to use the overload where you pass the view name
}
public PartialViewResult Alerts()
{
....
return PartialView(db.Alerts.ToList());
}
Dashboard.cshtml
<div class="dashboard_alerts">
#Html.Action("Alerts")
<div class="dashboard_pending_clubs">
#Html.Action("Clubs")
</div>
<div class="dashboard_verified_members">
#Html.Action("Members")
</div>
OR
You would need to create a ViewModel specific for the Dashboard page precisely this would be more efficient way
public class DashboardViewModel
{
public IEnumerable<GMC.Models.Clubs> Clubs { get; set; }
public IEnumerable<GMC.Models.MemberUsers> Users { get; set; }
public IEnumerable<GMC.Models.Alerts> Alerts { get; set; }
}
Then, in the Dashboard action method, you would populate each list:
myModel.Users = db.MemberUsers.ToList();
...
You would then need to update the view to take in this new ViewModel
#model DashboardViewModel
Finally, from within the view, you would need to pass in the data to each partial:
#Html.Partial("DashboardAlerts", Model.Alerts)
#Html.Partial("DashboardClubs", Model.Clubs)
I have a view which is bound with a ViewModel which contains multiple viewmodels.
Now, the parent view contains views(rendered by #html.partial) each view bound with its corresponding viewmodel and has its own form action.
My Question:
I could view the data correctly, but i can't submit each subview alone, so how can post each submodel alone?
Also, when there would be modelstate errors how can i refer to the correct subview?
Any idea would be appreciated.
Extra info:
The code sample shows what i did exactly:
ViewModel:
public class ViewModelParent
{
public ViewModelChild1 ViewModelC1 {get; set;}
public ViewModelChild2 ViewModelC2 {get; set;}
public ViewModelChild3 ViewModelC3 {get; set;}
}
Controller:
public ActionResult GetParent()
{
return view(new ViewModelParent());
}
Views:
GetParent.cshtml (contains views for each submodel).
#model Models.ViewModelParent
#Html.Partial("~/Views/Children/GetC1.cshtml", Model.ViewModelC1)
#Html.Partial("~/Views/Children/GetC2.cshtml", Model.ViewModelC2)
#Html.Partial("~/Views/Children/GetC3.cshtml", Model.ViewModelC3)
Children views:
GetC1.cshtml
#model ViewModelChild1
<form action="#Url.Action("GetC1", "Child"" method="POST" class="smart-form" id="frm_child1">
#Html.AntiForgeryToken()
#Html.ValidationSummary()
#* controls here*#
</form>
The same applies for the rest children views GetC2.cshtml & GetC3.cshtml
I've done something similar in the past.
I'd recommend this as a possible approach (assuming you want to stick with full page postbacks instead of going the ajax route).
Use your existing Parent ViewModel class (with child Models)
public class ViewModelParent
{
public ViewModelChild1 ViewModelC1 {get; set;}
public ViewModelChild2 ViewModelC2 {get; set;}
public ViewModelChild3 ViewModelC3 {get; set;}
}
Have the partial views each use the Parent Model
#model Models.ViewModelParent
#Html.Partial("~/Views/Children/GetC1.cshtml", Model)
#Html.Partial("~/Views/Children/GetC2.cshtml", Model)
#Html.Partial("~/Views/Children/GetC3.cshtml", Model)
The Child views each have the parent Model, but only contain form elements for the Child Model of that view. If you want a validation summary in every partial view you have to get a bit creative - I'll explain later...
eg: GetC1.cshtml
#model ViewModelParent
#using(Html.BeginForm("GetParent", "ParentControllerName", null, FormMethod.Post, new {#class="smart-form" id="frm_child1"}))
{
#Html.AntiForgeryToken()
#Html.ValidationSummaryForGroup(ViewBag.ChildType, "Child1") #* I'll explain this later *#
#* controls here - eg... *#
#Html.TextBoxFor(m => m.ViewModelChild1.Property1)
}
Then your controller can simply farm out the child methods if the form is valid (or return if not)
Eg:
public class ParentControllerNameController : Controller
{
public ActionResult GetParent()
{
return View(new ViewModelParent());
}
[HttpPost]
public ActionResult GetParent(ViewModelParent model)
{
if (ModelState.IsValid)
{
if (model.ViewModelC1 != null)
{
return GetC1(model.ViewModelC1);
}
else if (model.ViewModelC2 != null)
{
return GetC2(model.ViewModelC2)
}
else if (model.ViewModelC3 != null)
{
return GetC3(model.ViewModelC3)
}
} else {
// invalid!
if (model.ViewModelC1 != null)
{
ViewBag.ChildType = "Child1";
}
else if (model.ViewModelC2 != null)
{
ViewBag.ChildType = "Child2";
}
else if (model.ViewModelC3 != null)
{
ViewBag.ChildType = "Child3";
}
// needed to prevent null reference errors
if (model.ViewModelC1 == null) model.ViewModelC1 = new ViewModelChild1();
if (model.ViewModelC2 == null) model.ViewModelC2 = new ViewModelChild2();
if (model.ViewModelC3 == null) model.ViewModelC3 = new ViewModelChild3();
}
return View(model);
}
}
The above else-if statements will work, because each child view only contains properties for that child model - hence the other child viewmodels are null.
Note I used a new Html Helper extension above that I created that wraps the Validation Summary so you can display errors specific to the child model. A simple display/not display is insufficient because you'd lose client side validation errors being shown otherwise.
Of course this is only necessary when you have a validation summary in every partial view. If there's just one validation summary then you can stick with a simple #Html.ValidationSummary()
namespace System.Web.Mvc.Html
{
public static class ValidationSummaryForGroupExtensions
{
public static MvcHtmlString ValidationSummaryForGroup(this HtmlHelper html, string testValue, string expectedValue)
{
return ValidationSummaryForGroup(html, testValue, expectedValue, false);
}
/// <summary>
/// Displays a validation summary which shows serverside errors only if the specified testvalue and value are equal. Client side validation will work as normal.
/// <para>The purpose of this is to allow multiple valiation summaries (for multiple forms) on a single page.</para>
/// </summary>
/// <param name="testValue">Value to test (could be a value in viewbag)</param>
/// <param name="expectedValue">Value to expect if the server side errors are to be displayed.</param>
/// <returns></returns>
public static MvcHtmlString ValidationSummaryForGroup(this HtmlHelper html, string testValue, string expectedValue, bool excludePropertyErrors)
{
if (testValue != null && testValue.ToLower() == expectedValue.ToLower())
return html.ValidationSummary(excludePropertyErrors);
return new MvcHtmlString("<div class=\"validation-summary-valid\" data-valmsg-summary=\"true\"><ul><li style=\"display:none\"></li></ul></div>");
}
}
}
Of course you could do partial postbacks using ajax - in which case the child views could be directly for the child models, and each child form postback directly to the relevant method in your controller.
This is simple, in the handler of the post method of the controller, call the name of the parameter after the property name in the view model.
So in you view model you have:
public class ViewModelParent
{
public ViewModelChild1 viewModelC1 {get; set;}
public ViewModelChild2 viewModelC2 {get; set;}
public ViewModelChild3 viewModelC3 {get; set;}
}
In the post handler of your controller you will need something like:
<HttpPost()>
Function GetC1(viewModelC1 As ViewModelChild1 ) As ActionResult
in the html all the properties will be names like 'viewModelC1.nameofsomething' and this helps the model binder map the properties up. The above is VB.net but you should get the idea.
hope that helps
Andy
I am working on a ASP.NET MVC project. I have a question about the View in a CRUD operation.
In most of the examples I have seen, separate views for each CRUD operation (e.g. Add, Edit, Delete) are used. Now imagine if I have 100 tables in my database, and each of them requires CRUD operations via a View. Is it best to create these separate Views for each table or create a function that would create these Views for me, such as below?
public ActionResult CreateSubject()
{
return View(Model.toList());
}
public ActionResult EditSubject()
{
return View();
}
public ActionResult DeleteSubject()
{
return View();
}
I use separate actions for each operation on my controller and create a simple PartialView that handles all the fields and then I use a shared View from my Shared folder that loads my partial view.
public class CRUDController : Controller {
public ActionResult Create() {
return View(new CreateModel());
}
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Create(CreateModel model) {
if(ModelState.IsValid) {
//save to db return view or redirect
}
return View(model);
}
public ActionResult Edit(it id) {
var model = new EditModel();
model = //load and map from db
return View(model);
}
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Create(EditModel model) {
if(ModelState.IsValid) {
//save to db return view or redirect
}
return View(model);
}
}
Supporting interfaces:
public interface ICreateModel {
}
public interface IEditModel {
int Id {get;set;}
}
CreateModel.cs:
public class CreateModel : ICreateModel {
public string SomeProp {get;set;}
}
EditModel.cs:
public class EditModel : CreateModel, IEditModel {
public int Id {get;set;}
}
_create.cshtml:
#model CreateModel
#Html.TextBoxFor(x=>x.SomeProp)
Create.cshtml:
#model ICreateModel
#using(Html.BeginForm) {
#Html.Partial("_create")
<input type="submit" value="Submit"/>
}
Edit.cshtml
#model EditModel
#using(Html.BeginForm) {
#Html.Partial("_create")
#Html.HiddenFor(x=>x.Id)
<input type="submit" value="Submit"/>
}
This is an example of how I handle my multiple CRUD operations (at least in terms of showing forms for them). There would obviously be more content in your Create/Edit views
By placing the Edit.cshtml and Create.cshtml in the Shared folder, it'll be used by default when you return a view from an action with those names. By default the view engine checks the appropriate view folder for the controller for the file and then looks to Shared. Each of your _create.cshtml partial should be in the appropriately named View folders and they will be delivered correctly.
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'm new to MVC and I'm trying to add a partial view to my main layout page so I can show messages throughout my app. I'm having some trouble woring it out:
Here's my layout:
<div class="span12">
<p>
#{Html.RenderAction("Messaging", "Messaging");}
</p>
#RenderBody()
</div>
This is my messaging controller:
public ActionResult Messaging()
{
return PartialView(new ViewModels.Messaging()
{
MessageType = Utilities.MessageType.Success,
MessageHeader = "Test",
Message = "this is a test message"
});
}
Here is my partial view called _MessagingPartial.cshtml stored in the shared folder:
#model AWS.PL.ViewModels.Messaging
<span>
Model.Message
</span>
And here is my ViewModel:
public class Messaging
{
public Utilities.MessageType MessageType { get; set; }
public string MessageHeader { get; set; }
public string Message { get; set; }
}
I get the error "Partial view Messaging was not found" error. Should the partial view be called Messanging.cshtml or I'm I get the something fundamentally wrong?
Any help much appreciated.
Thanks,
Wilky.
Here is my partial view called _MessagingPartial.cshtml
Your partial view should be called Messaging.cshtml coz that's the name of the controller action that rendered it.
If you want to render a custom partial name, make sure you explicitly specify that:
public ActionResult Messaging()
{
var model = new ViewModels.Messaging
{
MessageType = Utilities.MessageType.Success,
MessageHeader = "Test",
Message = "this is a test message"
};
return PartialView("_MessagingPartial", model);
}
and if the partial is located in some non-standard location you could also specify the full path to it:
return PartialView("~/Views/FooBar/Baz/_MessagingPartial.cshtml", model);
Your guessing is right. Either partial view should be named after calling action, "Messanging.cshtml" in your case, or you should use overload to explicitly supply view name to View method
protected internal ViewResult View(
string viewName,
Object model
)