Been working on creating an interface to allow a modular approach to the UI, the background:
Allows users to drag and drop a module onto a div
jQUery posts back to controller with the module and panel names
Controller returns a JsonResult containing a view that has been rendered, specific to that module
Here is a picture of the UI so you can sort of see what I am doing:
Image
Now, what I am trying to do, is in that JsonResult (Which contains a string output of a view rendering), is save some data back to the model, and refresh that dynamically rendered view, so that just the panel (Where the view has been rendered) updates.
Sounds complicated i know, so here is some code:
[AcceptVerbs(HttpVerbs.Post)]
public JsonResult AddModule(string id, string returnTo)
{
string content = RenderView(id);
return Json(new { Target = returnTo, Content = content });
}
private string RenderView(string moduleName)
{
string result = "";
ContentModule module = (ContentModule)Activator.CreateInstance(Type.GetType("TrustMRM.BLL.ContentModules." + moduleName + ",TrustMRM.BLL"));
module.TrustID = Settings.Default.TrustID;
module.DataBind();
this.ViewData.Model = module;
using (var sw = new System.IO.StringWriter())
{
ViewEngineResult viewResult = ViewEngines.Engines.FindPartialView(this.ControllerContext, moduleName);
var viewContext = new ViewContext(this.ControllerContext, viewResult.View, this.ViewData, this.TempData, sw);
viewResult.View.Render(viewContext, sw);
result = sw.GetStringBuilder().ToString();
}
return result;
}
The above is what handles the 'drop' of the module. I have an abstract class, ContentModule, and an implementation called BLLForumModule, there is a matching view, BLLForumModule.cshtml, that gets built, and returned in that string, strongly bound tot he BLLForumModule.
What is rendered is a drop down list, equal to some data to configure that particular module:
#model TrustMRM.BLL.ContentModules.BLLForumModule
#{
Layout = null;
}
#if (Model.IsConfigured)
{
<span>I am configured</span>
}
else
{
using (Html.BeginForm("RefreshModule", "Home"))
{
<h3 class="panelHeader">#Html.DisplayTextFor(m => m.Title)</h3>
<span>Select group</span>
#Html.DropDownListFor(m => m.SelectedGroupID, Model.GroupSelection.Select(t => new SelectListItem { Text = t.GroupName, Value = t.GroupID.Value.ToString() }));
#Html.HiddenFor(x => x.ModuleID);
<input type="submit" value="Ok" />
}
}
Now, I am unsure of what to return, or how to handle this post in order to refresh that view, the one that was rendered as a string and sent back, any insight into this, and if anyone has done something similar before, perhaps my rendering the view to a string is the wrong approach?
The code to accept the form post:
public ActionResult RefreshModule(string ModuleID)
{
return View();
}
(Doesn't work)
Something like that will help you
Using Ajax.BeginForm with ASP.NET MVC 3 Razor
Just use Ajax.BeginForm and provide an id of replaced element.
Attach validation after ajax request here
MVC3 Unobtrusive Validation Not Working after Ajax Call
Related
I would like to create a view that would contain a different view. I've never used json before. How i can do this and How can I format the json data in the view?
My first function "Details" is to retrieve a object from the database and return view "Details.cshtml". In this view I want generates a partial view ("Stats.cshtml"). And now I want to generate a partial view with the data downloaded in the json format inside the Stats function.
Controller
public IActionResult Details(int? id = 1)
{
var person = _context.Persons.Find(id);
return View(champion);
}
public IActionResult Stats()
{
var json = new WebClient().DownloadString("url");
return Json(s);
}
View - Details.cshtml
#model Person
<div class=row">
<div class="col-sm-5"> #Model.Name </div>
<div class="col-sm-5"> #Html.Partial("Stats") </div>
</div>
View - Stats.cshtml
<h2>Stats</h2>
<div> here I want to put in a json field </div>
When I run "Stats" function from the address localhost/Home/Stats I get the result in json, but when I run "Details" function I get view "Details" and "Stats" without the json value.
to render a partial, you have many options, by your code,
the simplest one is: move your Stats code to Details action
public ActionResult Details()
{
...//prepare your person viewModel
var result = new WebClient().DownloadString("url");
var stats = JsonConvert.DeserializeObject<YourViewModel>(result);
//you have 2 options to return data
yourPersonModel.Stats=stats ; //<== you have to change PersonViewModel
//or ViewBag.Stats=stats;
return View(yourPersonModel);
}
then in Details.cshtml:
#Html.Partial("Stats", ViewBag.Stats or Model.Stats)//by your choice before.
Since Html.Action is removed, but ViewComponent comes in Core, you cannot directly call it right now, but this link will tell you how to make it back: #Html.Action in Asp.Net Core
public ActionResult Stats()
{
var result = new WebClient().DownloadString("url");
var yourViewModel = JsonConvert.DeserializeObject<YourViewModel>(result);
return PartialView(yourViewModel);
}
add the following code in your View - Stats.cshtml:
#model YourViewModel
then in Details.cshtml:
#Html.Action("Stats")
Be aware that Html.Action cannot call async actions, be careful to use it.
the next solution is to use new feature ViewComponent, here is details:
https://learn.microsoft.com/en-us/aspnet/core/mvc/views/view-components?view=aspnetcore-2.1
the last one will not be what you expected: use AJAX to load this partial page on page Details is loaded to client
I have multiple index views with a different grid in each of these views, but all of them uses the same popup control. I dont want to make a partial view foreach index view that i have. So i put the popup partial view in the Shared folder.
But i have a Html.BeginForm('Action','Controller') in the popup partialview, and these values are different in each grid. How can i pass these from the view of the grid to the partial view of the popup?
The Grid View:
//Code Resumed
#Html.DevExpress().GridView(
settings =>
{
settings.Name = "TestMasterGrid";
settings.Column.Add("Id");
settings.Column.Add("Name");
settings.Column.Add("Email");
//Command Column Wich calls the popup control
}
The PopUp PartialView:
//Code resumed
using (Html.BeginForm("ActionINeedToGetFromTheGridView", "ControllerINeedToGetFromTheGridView", FormMethod.Post))
{
Html.DevExpress().TextBox(
textBoxSettings =>
{
textBoxSettings.Name = "reason";
textBoxSettings.ControlStyle.CssClass = "editor";
})
.Render();
Html.DevExpress().Label(
labelSettings =>
{
labelSettings.Name = "sh";
labelSettings.ControlStyle.CssClass = "label";
}).Render();
Html.DevExpress().Button(
buttonSettings =>
{
buttonSettings.Name = "btnUpdate";
buttonSettings.ControlStyle.CssClass = "button";
buttonSettings.Width = 80;
buttonSettings.Text = "OK";
buttonSettings.UseSubmitBehavior = true;
}
)
.Render();
Thanks!
Pass the action and controller names to the action that returns the PartialViewResult. Then, pass the names to the partial's model and use them in the BeginForm statement:
Html.BeginForm(Model.Action, Model.Controller, FormMethod.Post)
Edit:
I'm not very familiar with DevExpress, but I found the CallbackRouteValues member in settings. I'll use that for my example:
settings.CallbackRouteValues = new { Controller = "ControllerName", Action = "GetPartialView", desiredAction = "DesiredAction", desiredController = "DesiredController" }
In your controller, you'd have the action and controller parameters:
public PartialViewResult GetParialView(string desiredAction, string desiredController) {
var viewModel = new PartialViewModel { Action = desiredAction, Controller = desiredController);
Return PartialView("Name", viewModel);
}
I typed out this code by hand, so it's probably full of errors. Hopefully it gets the idea across, though.
Quick edit: changed some parameter names to make it a little clearer.
Working with Multiple Steps on a form and different views sharing one model.
On my second Step Form Post submit I am passing in my model to maintain the state.
<% using (Html.BeginForm("StepTwo", "Home", Model, FormMethod.Post, new { id = "restrictionForm" })) { %>
But I find when I hit submit and it works the way I want, my entire model and its attributes are now listed in the url which is not desirable.
[Authorize]
[HttpPost]
[ActionName("StepTwo")]
[ValidateAntiForgeryToken]
public ActionResult StepTwoPost(PostcodesModel model)
{
try
{
_Provider.AddNewRestriction(model.Postcode, model.SelectedRestriction, model.RestrictionDescription, model.WildcardID);
return View("StepThree", model);
}
catch(Exception ex)
{
ViewBag.PostCodeErrors = "<div class=\"errorMessage\">Error inserting restriction " + model.Postcode + ".</div><p>" + ex.Message + "</p>";
return View(model);
}
}
Anyway to now display model in URL this way ?
i.e.
http://localhost:/Home/StepThree?Postcode=1234&.....
can you post the complete view?,
how are are you rendering your Models values? if you are rendering them as routing data within the submit button these will cause them to appear on the URL, you should render them the model within HTML elements "Textbox" or "Hidden" for example and these values will be posted as form data
I want to redirect to a different view depending on the result of a dataset, but I keep getting returned to the page I am currently on, and can't work out why. I drop into the if statement the action gets called but once i return the view to the new page, it returns me back to the current page.
CSHTML page
#{
ViewBag.Title = "Search Results";
EnumerableRowCollection<DataRow> custs = ViewBag.Customers;
bool anyRows = custs.Any();
if(anyRows == false)
{
Html.Action("NoResults","Home");
}
// redirect to no search results view
}
Controller
public ActionResult NoResults()
{
return View("NoResults");
}
View I cant get too..
#{
ViewBag.Title = "NoResults";
}
<h2>NoResults</h2>
Change to this:
#{ Response.Redirect("~/HOME/NoResults");}
Would be safer to do this.
#{ Response.Redirect("~/Account/LogIn?returnUrl=Products");}
So the controller for that action runs as well, to populate any model the view needs.
Source
Redirect from a view to another view
Although as #Satpal mentioned, I do recommend you do the redirecting on your controller.
This clearly is a bad case of controller logic in a view. It would be better to do this in a controller and return the desired view.
[ChildActionOnly]
public ActionResult Results()
{
EnumerableRowCollection<DataRow> custs = ViewBag.Customers;
bool anyRows = custs.Any();
if(anyRows == false)
{
return View("NoResults");
}
else
{
return View("OtherView");
}
}
Modify NoResults.cshtml to a Partial.
And call this as a Partial view in the parent view
#Html.Partial("Results")
You might have to pass the Customer collection as a model to the Result action or in a ViewDataDictionary due to reasons explained here: Can't access ViewBag in a partial view in ASP.NET MVC3
The ChildActionOnly attribute will make sure you cannot go to this page by navigating and that this view must be rendered as a partial, thus by a parent view. cfr: Using ChildActionOnly in MVC
You can go to method of same controller..using this line , and if you want to pass some parameters to that action it can be done by writing inside ( new { } )..
Note:- you can add as many parameter as required.
#Html.ActionLink("MethodName", new { parameter = Model.parameter })
Been picking up MVC4 and Razor and having a ball, but I've got a question on the approach for what I want to acheive:
I have a page with some panels on (Like a dashboard), and a set of icons you can drag and drop to these panels to 'install' a module into that panel, and display it's content. This is great from a UI point of view, now I'm looking at hooking this up to something a bit meatier:
What I have:
IContentModule
Set of concrete classes for each module with a Render() method
Controller that handles module drop event and an Activator to get an instance of the class for that module drop
Simple stuff really, ideally, I want it so that each module is responsible for it's own content, but aside from having a string return from Render, is there a better way, like, assigning a specific view markup to that particular concrete class, so that I can have control over what is being rendered, but in a much more structured way, wondering what the best approach is here?
Thanks for your time!
Danny
Edit: Sorta thinking if there was a way to couple a view to my concrete classes? e.g. ViewForum.cshtml binding to ForumModule.cs, somehow instantiating the view and getting a string from it's render of the object, then passing that back via a string to insert into my panel?
An example of a panel:
<section class="main box droppable" id="MainPanel">
<div class="padding">
Panel 1
</div>
</section>
The jQuery event
$(".droppable").droppable({
hoverClass: 'boxhover',
drop: function (event, ui) {
$.ajax({
type: "POST",
url: '/Home/AddModule/' + $(ui.draggable).attr("id") + "?returnTo=" + this.id,
success: function(data) {
$("#" + data.Target).html(data.Content);
}
});
}
});
The controller method
[AcceptVerbs(HttpVerbs.Post)]
public JsonResult AddModule(string id, string returnTo)
{
string content = DemoResolve(id);
try
{
IContentModule module = (IContentModule) Activator.CreateInstance(Type.GetType("Foo.Bar.BLLForumModule,Foo.Bar"));
content = module.Render();
}catch(Exception exp)
{
throw;
}
return Json(new { Target = returnTo, Content = content });
}
So where i have that module.Render(), i'm thinking I'd want to get a partial view or something and render that based on the object I have in hand
Worked with a colleague of mine and came up with a solution of binding the views and the modules together in a sort of dynamic way, for example, if the jQuery post comes back with the string of the module 'BLLForumModule' we have the following:
[AcceptVerbs(HttpVerbs.Post)]
public JsonResult AddModule(string id, string returnTo)
{
string content = DemoResolve(id);
try
{
content = RenderView("BLLForumModule");
}catch(Exception exp)
{
throw;
}
return Json(new { Target = returnTo, Content = content });
}
private string RenderView(string moduleName)
{
string result = "";
IContentModule module = (IContentModule)Activator.CreateInstance(Type.GetType("Foo.Bar." + moduleName +",Foo.Bar"));
this.ViewData.Model = module;
using (var sw = new System.IO.StringWriter())
{
ViewEngineResult viewResult = ViewEngines.Engines
.FindPartialView(this.ControllerContext, moduleName);
var viewContext = new ViewContext(this.ControllerContext,
viewResult.View, this.ViewData, this.TempData, sw);
viewResult.View.Render(viewContext, sw);
result = sw.GetStringBuilder().ToString();
}
return result;
}
This assumes there is a class in the Foo.Bar assembly with the same name as the view we are trying to load (BLLForumModule.cshtml and Foo.Bar.BLLForumModule.cs)
I then take the rendered content from the view and spit it back as a JsonResult as the Content part and the Target part of the JsonResult as the ID of the panel it needs to be dropped into.
This feels pretty good I think, any suggestions or improvements welcome!