I just started working with ASP MVC using DevExpress, I've created views that have GridViews inside, and I had the settings in the partial view.
Short story
I need to have the GridViewSettings object inside a controller rather than the view.
Why? Because the app needs to download an Excel file of the grid and so far this is the only approach I've seen. But the problem is, having the grid settings in the controller doesn't allow me to specify a label inside an unbound column.
Here's the code:
public GridViewSettings MyGridSettings()
{
var settings = new GridViewSettings();
settings.Name = "MyGrid";
settings.CommandColumn.Visible = true;
settings.KeyFieldName = "PERSON_ID";
settings.SettingsPager.Visible = true;
settings.Settings.ShowGroupPanel = true;
settings.Settings.ShowFilterRow = true;
settings.SettingsBehavior.AllowSelectByRowClick = true;
settings.SettingsAdaptivity.AdaptivityMode = GridViewAdaptivityMode.HideDataCellsWindowLimit;
settings.SettingsAdaptivity.AdaptiveColumnPosition = GridViewAdaptiveColumnPosition.Right;
settings.SettingsAdaptivity.AdaptiveDetailColumnCount = 1;
settings.SettingsAdaptivity.AllowOnlyOneAdaptiveDetailExpanded = false;
settings.SettingsAdaptivity.HideDataCellsAtWindowInnerWidth = 0;
settings.Columns.Add("PERSON_ID", "Person ID");
settings.Columns.Add(col =>
{
col.Caption = "Department";
col.SetDataItemTemplateContent(dataTemplate =>
{
String DepartmenID = (String)DataBinder.Eval(dataTemplate.DataItem, "DEPARTMENT_ID");
//if (DepartmenID != null)
//{
// Html.DevExpress().Label(label =>
// {
// label.Text = String.Format("{0}",
// DataBinder.Eval(dataTemplate.DataItem, "DEPARTMENT.NAME"));
// }).Render();
// }
}
}
}
Short Question
How do I access the following code from a Controller?
Html.DevExpress().Label(label =>
{
label.Text = String.Format("{0}",
DataBinder.Eval(dataTemplate.DataItem, "DEPARTMENT.NAME"));
}).Render();
Like I said I need to do this because the Excel File Download. Although, if you have any workarounds where I don't need to do this that'd be great
Using HtmlHelper class described here (read Using HtmlHelper class section), probably this code may solve your issue:
public GridViewSettings MyGridSettings(this HtmlHelper html)
{
var settings = new GridViewSettings();
settings.Name = "MyGrid";
// simplified for brevity
settings.Columns.Add(col =>
{
col.Caption = "Department";
col.SetDataItemTemplateContent(dataTemplate =>
{
String DepartmentID = (String)DataBinder.Eval(dataTemplate.DataItem, "DEPARTMENT_ID");
if (DepartmentID != null)
{
html.DevExpress().Label(label =>
{
label.Text = String.Format("{0}", DataBinder.Eval(dataTemplate.DataItem, "DEPARTMENT.NAME"));
}).Render();
}
}
}
return settings;
}
Unlike views, controller actions doesn't have reference to HtmlHelper by default, hence you need to include HtmlHelper class inside GridViewSettings method to create HTML helper extensions.
In case your DataItemTemplateContent can't be exported by design, try exporting GridView contents to XtraReport using How to convert and then print an GridView extension by using the XtraReport example.
Additional references:
Grid View Exporting - 1
Grid View Exporting - 2
Related
I have a scenario where 1 of 10 fields needs to be completed. When I add an error to each of the 10 properties, this results in the same error message appearing in the validation summary 10 times.
I have looked at this ValidationSummary displays duplicate messages
public static MvcHtmlString UniqueValidationSummary(this HtmlHelper html, bool excludePropertyErrors)
{
// do some filtering on html.ViewData.ModelState
return System.Web.Mvc.Html.ValidationExtensions.ValidationSummary(html, excludePropertyErrors);
}
But I am not sure how to actually get it working. When the extension function is run on page load html.ViewData.ModelState is valid and has no messages.
How can I strip out any duplicate error messages via this extension?
You have to write helper method that the following code.
public static IHtmlString UniqueValidationSummary(ModelStateDictionary ms)
{
var resultHtml = new StringBuilder();
resultHtml.Append("<div class=\"validation-summary-errors text-danger\" data-valmsg-summary=\"true\">");
resultHtml.Append("<ul>");
var isError = false;
var knownValues = new HashSet<string>();
foreach (var key in ms.Keys)
{
foreach (var e in ms[key].Errors)
{
isError = true;
if (!knownValues.Contains(e.ErrorMessage))
{
resultHtml.Append("<li>" + e.ErrorMessage + "</li>");
knownValues.Add(e.ErrorMessage);
}
}
}
if (!isError) return null;
resultHtml.Append("</ul>");
resultHtml.Append("</div>");
return new HtmlString(resultHtml.ToString());
}
And then, you can use helper method from view(.cshtml).
#MyHelper.UniqueValidationSummary(ViewData.ModelState);
I wouldn't usually recommend doing this but in some cases it might be needed.
I recently ran into a similar problem and needed to do the same.
Instead of trying to parse the ModelState in razor view, i did it in the controller, before returning the view. Here is the extension i used:
(Please Note that this hasnt been extensively tested, but seems to be working - i just wrote it)
using System.Collections.Generic;
using System.Linq;
using System.Web.Mvc;
namespace WebApplication.Common
{
public static class ModelStateExtension
{
public static void RemoveDuplicateErrorMessages(this ModelStateDictionary modelStateDictionary)
{
//Stores the error messages we have seen
var knownValues = new HashSet<string>();
//Create a copy of the modelstatedictionary so we can modify the original.
var modelStateDictionaryCopy = modelStateDictionary.ToDictionary(
element => element.Key,
element => element.Value);
foreach (var modelState in modelStateDictionaryCopy)
{
var modelErrorCollection = modelState.Value.Errors;
for(var i = 0 ; i < modelErrorCollection.Count ; i++)
{
//Check if we have seen the error message before by trying to add it to the HashSet
if (!knownValues.Add(modelErrorCollection[i].ErrorMessage))
{
modelStateDictionary[modelState.Key].Errors.RemoveAt(i);
}
}
}
}
}
}
You simple need to call the extension on your ModelState before returning the view:
using WebApplication.Common;
if (!ModelState.IsValid)
{
//viewModel code omitted
ModelState.AddModelError("0", "Server Side Validation failed");
ModelState.RemoveDuplicateErrorMessages();
return View(viewModel);
}
I want to create an ext.NET Panel in a back-end class somewhere in my project. Then, inside a razor view, I want to instantiate this class and use it to place the component to the screen. I need help with this latter part!
For example, I have the class:
namespace Interface.Resources
{
public class Screen
{
public ext.Panel NavigationPanel()
{
ext.Panel navPanel = new ext.Panel
{
Title = "NAVIGATION",
Border = true,
Header = false,
Region = ext.Region.East,
Width = 400,
Collapsible = true,
CollapseMode = ext.CollapseMode.Mini,
Split = true
};
return navPanel;
}
....
Now, the view class looks something like this...
#using Interface.Resources
#functions
{
Interface.Resources.Screen _irs = new Interface.Resources.Screen();
}
#_irs.NavigationPanel()
....
Clearly the latter is not working... it compiles and runs, but I get an error. What am I doing wrong... I have tried using Html.X() etc., but I can't seem to get the panel to render.
Note: If I use something like Html.X().Panel()....Items(items => { items.add(_irs.NavigationPanel()); }) ... it works, but I don't want to have to create the container Panel in the razor view every time I use the Interface Resource library that I am creating.
I appreciate that this issue is not Razor specific.
I think your only way is to build the config in your classes then call the ext extension methods to render the panel in the view.
The Definition
namespace Interface.Resources
{
public class Screen
{
public static Ext.Net.Panel NavigationPanelConfig()
{
return new Ext.Net.Panel.Config()
{
Title = "NAVIGATION",
Border = true,
Header = false,
Region = Ext.Net.Region.East,
Width = 400,
Collapsible = true,
CollapseMode = Ext.Net.CollapseMode.Mini,
Split = true,
};
}
}
}
The Render
#(Html.X().Panel(Interface.Resources.Screen.NavigationPanelConfig()))
The solution that I found was to use the view-port to place the panel into. It must have been something to do with the view chain that I was implementing in.
#(
Html.X().Viewport()
.Layout(LayoutType.Border)
.Items(items =>
{
items.Add(_menu.NavigationPanel(Model));
items.Add(_tabPanel.MainScreen());
})
)
In my view have this:
#{
var properties = db.StylesPropertyDefs.OrderBy(o => o.PropertyId);
}
..
..
#(Html.X().GridPanel()
.Title("Array Grid")
.ID("propertyGrid")
.Width(600)
.Height(350)
.Store(Html.X().Store()
.Model(Html.X().Model()
.Fields(
new ModelField("PropertyId", ModelFieldType.Int),
new ModelField("PropertyName", ModelFieldType.String),
new ModelField("PropertyShortName", ModelFieldType.String),
new ModelField("PropertyActiveFlag", ModelFieldType.Boolean)
)
).DataSource(properties)
..
..
And i have an action in the controller to add new property. The new property is added successful but I can refresh the PanelGrid (without refresh the whole page). Here is the controller:
[DirectMethod]
public ActionResult AddNewProperty(string propertyName, string propertyCode, bool propertyActive)
{
if (propertyName == "" || propertyCode=="")
{
X.Msg.Show(new MessageBoxConfig
{
Title = "Error",
Message = "The field name or code can not be empty.",
Buttons = MessageBox.Button.OK,
Icon = MessageBox.Icon.ERROR
});
return this.Direct();
}
//if all is ok add new property
var newOne = new StylesPropertyDef
{
PropertyActiveFlag = propertyActive,
PropertyName = propertyName,
PropertyShortName = propertyCode
};
var db = new TaosKnowledgeDataContext(DataUtils.GetConStringLocal());
db.StylesPropertyDefs.InsertOnSubmit(newOne);
db.SubmitChanges();
//reload properties
var properties = db.StylesPropertyDefs.OrderBy(o => o.PropertyId);
var theGrid = X.GetCmp<GridPanel>("propertyGrid");
//now i need refresh or reload the panel grid.
X.GetCmp<Window>("AddNewProperty").Close();
return this.Direct();
//return RedirectToAction("StyleProperties");
}
So, resuming, I need refresh the PanelGrid datasource(or store) from the controller.
Please can you help me?
Please try the following.
Set up an ID for the Store.
.ID("Store1")
Do the following in the controller action.
Store store = X.GetCmp<Store>("Store1");
store.DataSource = db.StylesPropertyDefs.OrderBy(o => o.PropertyId);
store.DataBind();
Ok. I solve.
I deleted the Datasource in Store and I put a proxy reader to Controller.
.Proxy(
Html.X().AjaxProxy()
.Url(Url.Action("Read"))
.Reader(Html.X().JsonReader().Root("data"))
)
In the controller:
public ActionResult Read()
{
var db = new TaosKnowledgeDataContext(DataUtils.GetConStringLocal());
var properties = db.StylesPropertyDefs.OrderBy(o => o.PropertyId);
return this.Store(properties);
}
And when insert new Property:
var store = X.GetCmp<Store>("Store1");
store.Reload();
Thanks anyways.
I am using ASP.NET MVC3 for a form that has both server and client validations. I'm showing error messages as balloons above the inputs. Due to the presentation of the errors, I need to only show one error at a time, otherwise the balloons tend to obscure other fields that may also be in error.
How can I customize the validation behavior to only render the first error message?
Edit: Please notice that the form has both server and client validations, and that I only want to show the first error message for the entire form (not per field).
In case anyone needs it, the solution I came up with is to add the following script towards the bottom of the page. This hooks into the existing javascript validation to dynamically hide all but the first error in the form.
<script>
$(function() {
var form = $('form')[0];
var settings = $.data(form, 'validator').settings;
var errorPlacementFunction = settings.errorPlacement;
var successFunction = settings.success;
settings.errorPlacement = function(error, inputElement) {
errorPlacementFunction(error, inputElement);
showOneError();
}
settings.success = function (error) {
successFunction(error);
showOneError();
}
function showOneError() {
var $errors = $(form).find(".field-validation-error");
$errors.slice(1).hide();
$errors.filter(":first:not(:visible)").show();
}
});
</script>
Could give this a shot on your controller action
var goodErrors = ModelState.GroupBy(MS => MS.Key).Select(ms => ms.First()).ToDictionary(ms => ms.Key, ms => ms.Value);
ModelState.Clear();
foreach (var item in goodErrors)
{
ModelState.Add(item.Key, item.Value);
}
I'm just selecting only one of each property error, clearing all errors then adding the individual ones back.
this is completely untested but should work.
You could create a custom validation summary which would display only the first error. This could be done either by creating an extension for the HtmlHelper class, or by writing your own HtmlHelper. The former is the more straightforward.
public static class HtmlHelperExtensions
{
static string SingleMessageValidationSummary(this HtmlHelper helper, string validationMessage="")
{
string retVal = "";
if (helper.ViewData.ModelState.IsValid)
return "";
retVal += #"<div class=""notification-warnings""><span>";
if (!String.IsNullOrEmpty(validationMessage))
retVal += validationMessage;
retVal += "</span>";
retVal += #"<div class=""text"">";
foreach (var key in helper.ViewData.ModelState.Keys)
{
foreach(var err in helper.ViewData.ModelState[key].Errors)
retVal += "<p>" + err.ErrorMessage + "</p>";
break;
}
retVal += "</div></div>";
return retVal.ToString();
}
}
This is for the ValidationSummary, but the same can be done for ValidationMessageFor.
See: Custom ValidationSummary template Asp.net MVC 3
Edit: Client Side...
Update jquery.validate.unobstrusive.js. In particular the onError function, where it says error.removeClass("input-validation-error").appendTo(container);
Untested, but change that line to: error.removeClass("input-validation-error").eq(0).appendTo(container);
Create a html helper extension that renders only one message.
public static MvcHtmlString ValidationError(this HtmlHelper helper)
{
var result = new StringBuilder();
var tag = new TagBuilder("div");
tag.AddCssClass("validation-summary-errors");
var firstError = helper.ViewData.ModelState.SelectMany(k => k.Value.Errors).FirstOrDefault();
if (firstError != null)
{
tag.InnerHtml = firstError.ErrorMessage;
}
result.Append(tag.ToString());
return MvcHtmlString.Create(result.ToString());
}
Update the jquery.validate.unobtrusive.js OnErrors function as below,
function onErrors(form, validator) { // 'this' is the form element
// newly added condition
if ($(form.currentTarget).hasClass("one-error")) {
var container = $(this).find(".validation-summary-errors");
var firstError = validator.errorList[0];
$(container).html(firstError.message);
}
else {
var container = $(this).find("[data-valmsg-summary=true]"),
list = container.find("ul");
if (list && list.length && validator.errorList.length) {
list.empty();
container.addClass("validation-summary-errors").removeClass("validation-summary-valid");
$.each(validator.errorList, function () {
$("<li />").html(this.message).appendTo(list);
});
}
}
}
Basically we have added a condition in the OnError to check whether the form contains a css-class named one-error and if yes then displays a single error else display all.
I want to map all CMS pages url to single controller(PageController) and action(Details).
How can I create custom routing to map all these urls?
/teacher
/teacher/kindergarten
/teacher/kindergarten/1
/teacher/primary
/teacher/primary/english
/teacher/primary/language
/teacher/primary/language/chinese
/teacher/primary/math
/teacher/primary/science
/parent
/parent/kindergarten
/parent/primary1-3
/parent/primary4-6
/leader
/leader/kindergarten
/leader/kindergarten/1
If you have these URLs in a database you could map the routes when the application starts up:
var pages = siteDB.Pages.ToList();
string pagePath = "";
foreach (var page in pages)
{
routeVals = new RouteValueDictionary();
constraints = new RouteValueDictionary();
routeVals.Add("controller", "page");
routeVals.Add("action", "details");
constraints.Add("path", "[a-zA-Z0-9\\-]*");
// any child pages? must add these routes before their parent pages.
var childPages = siteDB.Pages.Where(p => p.ParentPageId == page.PageId).ToList();
foreach (var childPage in childPages)
{
pagePath = BuildPath(childPage);
RouteTable.Routes.Add(new Route(pagePath, new MvcRouteHandler())
{
Defaults = routeVals,
Constraints = constraints,
DataTokens =
new RouteValueDictionary {
{ "pageid", childPage.PageId },
{ "path", pagePath }
}
});
// Any further child pages? (Only 3 levels supported)
var childSubPages = siteDB.Pages.Where(p => p.ParentPageId == childPage.PageId).ToList();
foreach (var childSubPage in childSubPages)
{
pagePath = BuildPath(childSubPage);
RouteTable.Routes.Add(new Route(pagePath, new MvcRouteHandler())
{
Defaults = routeVals,
Constraints = constraints,
DataTokens =
new RouteValueDictionary {
{ "pageid", childSubPage.PageId },
{ "path", pagePath }
}
});
}
}
This code takes the pages from a database where they are linked by parent id.
Here's the BuildPath function which generates a full path to each page:
public static string BuildPath(Page page)
{
if (page.ParentPageId == 1)
{
return page.PageKey;
}
else
{
SiteDataEntities siteDB = new SiteDataEntities();
string path = page.PageKey;
Page parent = siteDB.Pages.Find(page.ParentPageId);
while (parent != null)
{
path = parent.PageKey + "/" + path;
parent = siteDB.Pages.Find(parent.ParentPageId);
if (parent.PageKey == "home") break;
}
return path;
}
}
Previous proposed solution is working only for small amount of pages.
Because according to the code:
application generate and register Route for each of site page. In result we have at least same amount of routes as pages in our site. As you probably know RouteModule have to check route by route each of them to find first right one and execute correct handler, controller, action, view...
There are two other way to solve this:
You can create a class that derives from RouteBase and implement the properties and methods that you need: split url to segments, determinate current page fill RouteValueDictionary with pageid, path, parents etc
You can customize UrlRewriteModule with custom rewrite provider. Idea to transform all requests url from tree base structure to mvc default route:
{controller}/{action}/{id}?path=parentlevel1/parent2/parent3/....
90% -same code for both variants could be prepared.
that solution also could be useful when you have different controllers, correct one we could determinate by current page (by page data: type)