I have the following code that is used in a Razor view to configure grid settings for a DevExpress GridView MVC extension. All is good with the code, but now I wish to do data exports from the grid, which require the same configuration code on the 'server', i.e. upstream of the view. I could quite easily do this and provide a GridSettings property on my view model, if it were not for the required access to the WebViewPage<TModel>.ViewContext property.
Right now I am using a really ugly workaround and passing ViewContext back into the controller from the view; the controller then builds the grid settings. Needless to say the view calling a method on the controller is rather smelly.
settings.Columns.Add(column =>
{
column.Caption = "#";
column.SetDataItemTemplateContent(c =>
{
ViewContext.Writer.Write(
Html.ActionLink("Edit", "Edit", new {id = DataBinder.Eval(c.DataItem, "Id")}) + " " +
Html.ActionLink("Delete", "Delete", new {id = DataBinder.Eval(c.DataItem, "Id")},
new {onclick = "return confirm('Do you really want to delete this record? [Just say no!]')"})
);
});
column.SetHeaderTemplateContent(c => ViewContext.Writer.Write(Html.ActionLink("New", "Create")));
column.Settings.AllowDragDrop = DefaultBoolean.False;
column.Settings.AllowSort = DefaultBoolean.False;
column.Width = 70;
});
settings.Columns.Add("RefNum", "Emp. No.");
In general, it is necessary to have only the "Name" property of the exported GridViewSettings object on both the PartialView and Controller sides.
The GridView's PartialView should also be wrapped with a form.
See this example in the DX code library and this thread.
It is possible to specify shared GridViewSettings on the Controller side within a static object (like in this demo).
Related
I am using Devexpress MVC application where i used one GridLookup control in shared layout. I needed here some controller which will call a method on every request. For this purpose i used base controller and using ActionExecutingContext method where i am calling my menu loading and gridlookup loading. I am using viewdata to set the value and in shared view i used partial view of my GridLookup control where i am binding viewdata to GridLookup.
Below is the Base controller used to load menu and filters of gridlookup.
protected override void OnActionExecuting(ActionExecutingContext context)
{
base.OnActionExecuting(context);
ProcessingMenus();
}
Below is the shared layout where i am using partialview of gridlookup control
#Html.Partial("_ReportFilter")
Below is the GridLookup control used in this partial:
#{
var gridLookup = Html.DevExpress().GridLookup(settings =>
{
settings.Name = "LookupLobMultiple";
settings.KeyFieldName = "Description";
settings.GridViewProperties.CallbackRouteValues = new { Controller = "Manage", Action = "BindLOB" };
settings.Properties.SelectionMode = GridLookupSelectionMode.Multiple;
settings.Properties.TextFormatString = "{0}";
settings.Properties.MultiTextSeparator = ";";
settings.CommandColumn.Visible = true;
settings.CommandColumn.ShowSelectCheckbox = true;
settings.CommandColumn.SelectAllCheckboxMode = GridViewSelectAllCheckBoxMode.AllPages;
settings.GridViewProperties.SettingsPager.Visible = false;
settings.GridViewProperties.Settings.ShowGroupPanel = false;
settings.GridViewProperties.Settings.ShowFilterRow = false;
settings.Columns.Add("ID").Visible = false;
settings.Columns.Add("Description").Caption = "Line of Business";
settings.PreRender = (s, e) =>
{
MVCxGridLookup gl = (MVCxGridLookup)s;
gl.GridView.Selection.SelectRowByKey(ViewData["LOB"]);
};
});
}
#gridLookup.BindList(ViewData["LobModal"]).GetHtml()
In the above GridLookup control you can see am binding data using viewdata which is loading in ProcessingMenus method.
First issue here is in GridLookup i have used controller and action method also but this is not calling when i check and uncheck any value and showing Loading....
Second issue when after sometime if i again hit the url OnActionExecuting method is not calling due to it menus are not loading again.
I found the answer from Devexpress team is to call partial view in shared view use #{Html.RenderAction("action", "controller");} and then in that action call the partial view that need to show in shared layout with passing model data.
and in partial view just bind the grid with the passed model.
That's it.
Thank you for all your suggestions.
I'm new in MVC and have a question of principle about the way how the model is passed back from the view to the controller.
In the usual way the model-object comes from the controller, "spreads" its data into fields of the view and is then gone. The data will then be re-collected by a FormCollection-object passed back to the controller.
But there is also the other way where the model-object itself can be passed back to the controller as routedObject of e.g. an ActionLink, URL-Action or whatever. Then it's not necessary to spread and re-collect all the data.
In my work-place the other way is blocked, I get a warning about forbidden chars in the link-string. When investigating the issue I found that the other way seems mostly unknown.
For many reasons I think it's much better to pass the model-object back instead of the elaborate re-collecting of data.
So what is the reason for this curiosity please?
Update: Added View-Example
#model MvcApplication2.Models.TestClass
#{
#(Model.TestValue = 111);
}
<a href="#Url.Action("ValueBack", Model)">
<span>Test</span>
</a>
public void ValueBack(MvcApplication2.Models.TestClass testClass)
{
int x = testClass.TestValue;
}
In MVC we can get the values of the form in the controller by 3 ways.
Using the FormCollection Object.
Using the Parameters.
Strongly type model binding to view.
And I think it is not possible to send the model to the controller through the actionlink (as an ActionLink helper generates an anchor tag which when clicked sends a GET request to the server)
The Another Way :
Send the id of current model so that the controller action can fetch it back from the datastore from which it initially fetched it when rendering the View.
View:
#Ajax.ActionLink(
"Next",
"Step",
new {
StepId = 2,
id = Model.Id
},
new AjaxOptions { UpdateTargetId = "stepContainer" },
new { #class = "button" }
)
Controller:
public ActionResult Step(int StepId, int id)
{
var model = Repository.GetModel(id);
//Code
}
I am working on a dynamic menu system for MVC and just to make it work, I created a partial view for the menu and it works great using the syntax below:
#Html.RenderPartial("_Menu", (Models.Menu)ViewBag.MainMenu)
BUT, to do so, I would have to set the MainMenu and FooterMenu (or any other menu for that matter) in the ViewBag on each Controller and each action. To avoid this, I was wondering if there was a recommended event that I could access the ViewBag globally. If not, does anyone recommend passing the Menu object into a session variable? It doesn't sound right to me, but only thing I can think of right now.
UPDATE:
_Layout.cshtml - I included the new call to Action:
#Html.Action("RenderMenu", "SharedController", new { name = "Main" })
SharedController.cs - Added New Action:
public ActionResult RenderMenu(string name)
{
if (db.Menus.Count<Menu>() > 0 && db.MenuItems.Count<MenuItem>() > 0)
{
Menu menu = db.Menus.Include("MenuItems").Single<Menu>(m => m.Name == name);
return PartialView("_MenuLayout", menu);
}
else
{
return PartialView("_MenuLayout", null);
}
}
And it throws the following exception:
The controller for path '/' was not found or does not implement
IController.
UPDATE 2:
So, the issue is that I referenced the Controller by the full name and you only need the name of the controller minus "Controller". Neat tidbit. So, for my example, this works:
#Html.Action("RenderMenu", "Shared", new { name = "Main" })
set your menu up as an action then call it in your master layout.
use #Html.Action()
the action can return a partial view with your menu code in it.
I have an MVC app where users fill in a 4-step form then go to a "confirm" screen. On the confirm screen, if they select to modify their info I use RedirectToAction to take them back to the first step view, and I pass a URL parameter "modify=true", which tells the controller to use the session object already created as opposed to creating a new object from the DB and displaying an empty form. But once they submit the form for step 1 I want to send them from my controller to the step 2 view along with the "modify=true" parameter. There doesn't seem to be a way to return a viewmodel to a view and also pass a query string parameter. How can I accomplish this?
I have considered adding a bool to the viewmodels to signify "inReview" but i use different viewmodels for each of these views and they're all pretty clean, it seems like this bool would muck things up a bit.
I have also considered adding the bool to viewbag or viewdata, but then i'd be using the submit button to pass that value and the "modify=true" parameter would drop off the URL, possibly confusing the user and definitely confusing the code.
Thanks
If you use the Html.BeginForm() helper (without parameters) it will automatically append existing query string parameters to the generated form action attribute. If you use some of the other overloads such as Html.BeginForm("SomeAction", "SomeController", FormMethod.Post) then you're gonna lose those parameters. This could be easily fixed by writing a custom helper that will take into account those parameters:
public static class HtmlHelpers
{
public static IDisposable BeginRequestForm(this HtmlHelper html, string action, string controller, FormMethod method)
{
var builder = new TagBuilder("form");
var urlHelper = new UrlHelper(html.ViewContext.RequestContext);
var routeValues = new RouteValueDictionary();
var query = html.ViewContext.HttpContext.Request.QueryString;
foreach (string key in query)
{
routeValues[key] = query[key];
}
builder.MergeAttribute("action", urlHelper.Action(action, controller, routeValues));
builder.MergeAttribute("method", HtmlHelper.GetFormMethodString(method), true);
html.ViewContext.Writer.Write(builder.ToString(TagRenderMode.StartTag));
return new MvcForm(html.ViewContext);
}
}
and then use in your view (after bringing it into scope of course):
#using (Html.BeginRequestForm("SomeAction", "SomeController", FormMethod.Post))
{
...
}
You can either use ViewBag or your view model. You just need to pass the value somehow to the view:
ViewBag.modify = true;
return View(model);
Then in your view:
Html.BeginForm("MyAction", "MyController", new { modify = ViewBag.modify })
I've been scouring the web for a way to do this.
I want to generate a hyperlink to an action from my controller and put it in a string. I need to be able to define the label and give it html attributes. I can get Url.Action(...) working but that method doesn't let me define the label on the link.
HtmlHelper.GenerateLink(...) looks promising but I can't find any concrete examples on how to use it.
The link should look something like this:
View
Add this property to your base controller:
protected HtmlHelper Html
{
get
{
var viewContext = new ViewContext( ControllerContext, new WebFormView( Request.CurrentExecutionFilePath ),
new ViewDataDictionary(), new TempDataDictionary(), Response.Output )
{
RouteData = ControllerContext.RouteData
};
return new HtmlHelper( viewContext, new ViewPage() );
}
}
and then call it from anywhere:
var link = Html.ActionLink( "Click Me", "action" );
try this
string str = string.Concat("View"
and then pass this in ViewData and call it in view
<%= str%>
there are a few ways to do this - here are 2:
Link name here
Html.ActionLink(article.Title,
"Login", // <-- Controller Name.
"Item", // <-- ActionMethod
new { id = "<arguments here" }, // <-- Route arguments.
null // <-- htmlArguments .. which are none. You need this value
// otherwise you call the WRONG method ...
// (refer to comments, below).
)
there are other overloads of each available
Perhaps a little more information on why you would want to do this would be a little more helpful. If you return a string that contains HTML it will by default be HTML encoded and rendered useless on the client. If you have a custom view where this will be rendered why not build the link there using #Html.ActionLink?
I guess I am trying to figure out the benefit of doing it in the controller rather than the view...