How to create custom HtmlHelper for nullable Boolean radio button group in .NET Core - asp.net-mvc-4

Trying to make a custom MVC control
[Display(Name = "Do you agree?")]
[Required]
public bool? Agree { get; set; }

I have the following solution that I came up with
using Microsoft.AspNetCore.Html;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
using System;
using System.Linq.Expressions;
using System.Text;
namespace MyNameSpace
{
public static partial class HtmlHelpers
{
public static IHtmlContent RadioButtonsBooleanFor<TModel, TProperty>(this IHtmlHelper<TModel> html, Expression<Func<TModel, TProperty>> expression)
{
var expressionProvider = new ModelExpressionProvider(html.MetadataProvider);
var metadata = expressionProvider.CreateModelExpression(html.ViewData, expression);
var type = metadata.ModelExplorer.ModelType;
if (!(type == typeof(bool) || type == typeof (bool?)))
throw new InvalidCastException("Property must be either bool or bool?.");
var value = (bool?)metadata.Model;
var isYesChecked = value ?? false ? "checked=\"checked\"" : String.Empty;
var isNoChecked = !(value ?? true) ? "checked=\"checked\"" : String.Empty;
var validationMessage = $"The {metadata.Metadata.DisplayName} field is required.";
var output = new StringBuilder();
output.AppendLine("<div class=\"boolRadio\">");
output.AppendLine($"<input data-val=\"true\" data-val-required=\"{validationMessage}\" id =\"{metadata.Name}_yes\" name=\"{metadata.Name}\" type=\"radio\" value=\"True\" {isYesChecked}/><span>Yes</span>");
output.AppendLine($"<input id=\"{metadata.Name}_no\" name=\"{metadata.Name}\" type=\"radio\" value=\"False\" {isNoChecked}/><span>No</span>");
output.AppendLine($"<label for=\"{metadata.Name}_yes\">{metadata.Metadata.DisplayName}</label>");
output.AppendLine("</div>");
output.AppendLine($"<span class=\"field-validation-valid text-danger \" data-valmsg-for=\"{metadata.Name}\" data-valmsg-replace=\"true\"></span>");
return new HtmlString(output.ToString());
}
}
}
css:
.boolRadio{
height: 20px;
}
.boolRadio > span{
margin: 0 10px 0 5px;
}
To use with model property Agree
#Html.RadioButtonsBooleanFor(m=>m.Agree)

Related

Custom Model as Generic TypeArgument in XAML

I have made a small class, which inherits from DataGrid and takes in classes that derive from a specific interface:
public class RecordDataGrid<T> : DataGrid where T : IRecord
{
public RecordDataGrid()
{
this.AutoGenerateColumns = false;
this.CanUserAddRows = false;
this.CanUserDeleteRows = false;
this.CanUserResizeRows = false;
this.IsReadOnly = true;
this.SelectionMode = DataGridSelectionMode.Single;
this.Margin = new System.Windows.Thickness(0, 10, 0, 0);
var propertyInfos = typeof(T).GetProperties();
var list = new Dictionary<PropertyInfo, DataGridColumnAttribute>();
foreach (var propertyInfo in propertyInfos)
{
var customAttributes = propertyInfo.GetCustomAttributes(true);
foreach (var customAttr in customAttributes)
{
if (customAttr != null && customAttr is DataGridColumnAttribute)
{
list.Add(propertyInfo, (DataGridColumnAttribute)customAttr);
}
}
}
var ordered = (from entry in list orderby entry.Value.OrderIndex ascending select entry).ToDictionary(e => e.Key, e => e.Value);
foreach (var kvp in ordered)
{
var propertyInfo = kvp.Key;
var dgcAttr = kvp.Value;
var column = new DataGridTextColumn();
column.Header = dgcAttr.DisplayName;
column.Binding = new Binding(propertyInfo.Name);
column.Binding.StringFormat = dgcAttr.StringFormat ?? null;
column.Width = dgcAttr.ColumnWidthType == DataGridColumnAttribute.ColumnWidthTypes.Auto ? new DataGridLength(10, DataGridLengthUnitType.Auto) : new DataGridLength(10, DataGridLengthUnitType.Star);
this.Columns.Add(column);
}
}
}
It is very rough at the moment, just testing a few things out. The goal is to make my life easier by letting the DataGrid fill the Columns by itself, based on a custom Attribute:
public class DataGridColumnAttribute : Attribute
{
public string DisplayName { get; private set; }
public string StringFormat { get; private set; }
public ColumnWidthTypes ColumnWidthType { get; private set; }
public int OrderIndex { get; private set; }
public DataGridColumnAttribute(string displayName, int orderIndex, string stringFormat = null, ColumnWidthTypes columnWidthType = ColumnWidthTypes.Auto)
{
DisplayName = displayName;
StringFormat = stringFormat;
OrderIndex = OrderIndex;
ColumnWidthType = columnWidthType;
}
public enum ColumnWidthTypes
{
Auto,
Fill
}
}
Later on, as far as I am concerned, I should be able to use it in xaml like this:
Namespaces:
xmlns:model="clr-namespace:NickX.KswErp.Model.Classes;assembly=NickX.KswErp.Model"
xmlns:ctrl="clr-namespace:NickX.KswErp.ClientApplication.UI.Controls"
Control:
<ctrl:RecordDataGrid x:Name="_gridTransactions" x:TypeArguments="model:TransactionRecord" />
But I get following compilation error:
Only a master tag can specify the "x: TypeArguments" attribute.
(Roughly translated by google translation)
Maybe my approach is completely wrong tho. Should I do it completle in code behind. Or are there better approaches? Please let me know!
Conveniently I just found a thread in a german forum, which answeres my exact question. So people questioning the same in the future:
It is not possible. Easiest thing to do at this point is making a specific class for each model, which again derives from your generic class.
In my case:
public class TransactionDataGrid : RecordDataGrid<TransactionRecord>
{
}
Doesen't seem like a nice solution to me, and probably isn't the best way to do it. But it works.

Accessing ViewData in Layout for page with partial view not working

From a partial view, I want to include scripts or styles and have them rendered into the header or footer (instead of inline) so I have a taghelper and htmlextension that works when I used TempData in the htmlextensions, but if I use ViewData, it doesn't work. Any ideas why?
Partial view:
<style asp-resource-location="Header">
.partial1 {
background-color: red;
}
</style>
<h2>Test Partial</h2>
<script asp-resource-location="Footer">
alert("Partial1");
</script>
Htmlextensions:
public static IHtmlContent InlineScripts(this IHtmlHelper html, Enums.ResourceLocation location)
{
var result = new StringBuilder();
var scripts = html.ViewData.ContainsKey(location.ToString()) ? html.ViewData[location.ToString()] as List<string> : new List<string>();
foreach (var script in scripts)
{
result.Append(script);
}
var tag = new TagBuilder(location == Enums.ResourceLocation.Header ? "style" : "script");
tag.InnerHtml.SetHtmlContent(result.ToString());
return tag;
}
public static void AddInlineScriptParts(this IHtmlHelper html, Enums.ResourceLocation location, string script)
{
var scripts = html.ViewData.ContainsKey(location.ToString()) ? html.ViewData[location.ToString()] as List<string> : new List<string>();
scripts.Add(script);
html.ViewData[location.ToString()] = scripts;
}
Layout page:
#Html.InlineScripts(Enums.ResourceLocation.Header)
Style Taghelper:
public override void Process(TagHelperContext context, TagHelperOutput output)
{
if (Location != Enums.ResourceLocation.Header)
return;
var viewContextAware = _htmlHelper as IViewContextAware;
viewContextAware?.Contextualize(ViewContext);
var style = output.GetChildContentAsync().Result.GetContent();
_htmlHelper.AddInlineScriptParts(Location, style);
output.SuppressOutput();
}
I used ViewContext.HttpContext.Items and it works. I figured it would be better to use than TempData which uses session. If anyone has any reason why I shouldn't use this, please let me know. And if anyone knows why ViewData doesn't work, I would be interested to know also.

Issue with Web Api Custom Model Binder in MVC4

I am using Mvc4 with WebApi.
I am using Dto objects for the webApi.
I am having enum as below.
public enum Status
{
[FlexinumDefault]
Unknown = -1,
Active = 0,
Inactive = 100,
}
Dto structure is as follows.
[DataContract]
public class abc()
{
[DataMemebr]
[Required]
int Id{get;set;}
[DataMember]
[Required]
Status status{get;set}
}
I have created Custom Model Binder which will validate the enum(status) property in the dto object and return false if the enum value is not passed.
if the status enum property is not passed in the dto object,we should throw exception
public bool BindModel(System.Web.Http.Controllers.HttpActionContext actionContext, System.Web.Http.ModelBinding.ModelBindingContext bindingContext)
{
var input = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
if (input != null && !string.IsNullOrEmpty(input.AttemptedValue))
{
if (bindingContext.ModelType == typeof(Enum))
{
//var actualValue = null;
var value = input.RawValue;
in the api controller,i have action method like
public void Create([FromUri(BinderType = typeof(EnumCustomModelBinder))]abcdto abc)
{
In global.asax.cs
i have set like
GlobalConfiguration.Configuration.BindParameter(typeof(Enum), new EnumCustomModelBinder());
the issue i am facing is the custombinder
var input = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
,the input value is coming as null.
Please sugggest
I found the solution
This works fine,but the default implementation of model binder is missing.
public bool BindModel(System.Web.Http.Controllers.HttpActionContext actionContext, ModelBindingContext bindingContext)
{
var json = actionContext.Request.Content.ReadAsStringAsync().Result;
if (!string.IsNullOrEmpty(json))
{
var jsonObject = (JObject) Newtonsoft.Json.JsonConvert.DeserializeObject(json);
var jsonPropertyNames = jsonObject.Properties().Select(p => p.Name).ToList();
var requiredProperties = bindingContext.ModelType.GetProperties().Where(p =>p.GetCustomAttributes(typeof(RequiredAttribute),
false).Any()).ToList();
var missingProperties = requiredProperties.Where(bindingProperty => !jsonPropertyNames.Contains(bindingProperty.Name)).ToList();
if (missingProperties.Count > 0)
{
missingProperties.ForEach(
prop =>
{
if (prop.PropertyType.IsEnum)
actionContext.ModelState.AddModelError(prop.Name, prop.Name + " is Required");
});
}
var nullProperties = requiredProperties.Except(missingProperties).ToList();
if (nullProperties.Count > 0)
{
nullProperties.ForEach(p =>
{
var jsonvalue = JObject.Parse(json);
var value = (JValue)jsonvalue[p.Name];
if (value.Value == null)
{
actionContext.ModelState.AddModelError(p.Name, p.Name + " is Required");
}
});
}
}
// Now we can try to eval the object's properties using reflection.
return true;
}

Web API Help pages - customizing Property documentation

I have my web api and I added the web api help pages to auto-generate my documentation. It's working great for methods where my parameters are listed out, but I have a method like this:
public SessionResult PostLogin(CreateSessionCommand request)
And, on my help page, it is only listing the command parameter in the properties section. However, in the sample request section, it lists out all of the properties of my CreateSessionCommand class.
Parameters
Name | Description | Additional information
request | No documentation available. | Define this parameter in the request body.
I would like it instead to list all of the properties in my CreateSessionCommand class. Is there an easy way to do this?
So, I managed to devise a workaround for this problem, in case anyone is interested.
In HelpPageConfigurationExtensions.cs I added the following extension method:
public static void AlterApiDescription(this ApiDescription apiDescription, HttpConfiguration config)
{
var docProvider = config.Services.GetDocumentationProvider();
var addParams = new List<ApiParameterDescription>();
var removeParams = new List<ApiParameterDescription>();
foreach (var param in apiDescription.ParameterDescriptions)
{
var type = param.ParameterDescriptor.ParameterType;
//string is some special case that is not a primitive type
//also, compare by full name because the type returned does not seem to match the types generated by typeof
bool isPrimitive = type.IsPrimitive || String.Compare(type.FullName, typeof(string).FullName) == 0;
if (!isPrimitive)
{
var properties = from p in param.ParameterDescriptor.ParameterType.GetProperties()
let s = p.SetMethod
where s.IsPublic
select p;
foreach (var property in properties)
{
var documentation = docProvider.GetDocumentation(new System.Web.Http.Controllers.ReflectedHttpParameterDescriptor()
{
ActionDescriptor = param.ParameterDescriptor.ActionDescriptor,
ParameterInfo = new CustomParameterInfo(property)
});
addParams.Add(new ApiParameterDescription()
{
Documentation = documentation,
Name = property.Name,
Source = ApiParameterSource.FromBody,
ParameterDescriptor = param.ParameterDescriptor
});
}
//since this is a complex type, select it to be removed from the api description
removeParams.Add(param);
}
}
//add in our new items
foreach (var item in addParams)
{
apiDescription.ParameterDescriptions.Add(item);
}
//remove the complex types
foreach (var item in removeParams)
{
apiDescription.ParameterDescriptions.Remove(item);
}
}
And here is the Parameter info instanced class I use
internal class CustomParameterInfo : ParameterInfo
{
public CustomParameterInfo(PropertyInfo prop)
{
base.NameImpl = prop.Name;
}
}
Then, we call the extension in another method inside the extensions class
public static HelpPageApiModel GetHelpPageApiModel(this HttpConfiguration config, string apiDescriptionId)
{
object model;
string modelId = ApiModelPrefix + apiDescriptionId;
if (!config.Properties.TryGetValue(modelId, out model))
{
Collection<ApiDescription> apiDescriptions = config.Services.GetApiExplorer().ApiDescriptions;
ApiDescription apiDescription = apiDescriptions.FirstOrDefault(api => String.Equals(api.GetFriendlyId(), apiDescriptionId, StringComparison.OrdinalIgnoreCase));
if (apiDescription != null)
{
apiDescription.AlterApiDescription(config);
HelpPageSampleGenerator sampleGenerator = config.GetHelpPageSampleGenerator();
model = GenerateApiModel(apiDescription, sampleGenerator);
config.Properties.TryAdd(modelId, model);
}
}
return (HelpPageApiModel)model;
}
The comments that are used for this must be added to the controller method and not the properties of the class object. This might be because my object is part of an outside library
this should go as an addition to #Josh answer. If you want not only to list properties from the model class, but also include documentation for each property, Areas/HelpPage/XmlDocumentationProvider.cs file should be modified as follows:
public virtual string GetDocumentation(HttpParameterDescriptor parameterDescriptor)
{
ReflectedHttpParameterDescriptor reflectedParameterDescriptor = parameterDescriptor as ReflectedHttpParameterDescriptor;
if (reflectedParameterDescriptor != null)
{
if (reflectedParameterDescriptor.ParameterInfo is CustomParameterInfo)
{
const string PropertyExpression = "/doc/members/member[#name='P:{0}']";
var pi = (CustomParameterInfo) reflectedParameterDescriptor.ParameterInfo;
string selectExpression = String.Format(CultureInfo.InvariantCulture, PropertyExpression, pi.Prop.DeclaringType.FullName + "." + pi.Prop.Name);
XPathNavigator methodNode = _documentNavigator.SelectSingleNode(selectExpression);
if (methodNode != null)
{
return methodNode.Value.Trim();
}
}
else
{
XPathNavigator methodNode = GetMethodNode(reflectedParameterDescriptor.ActionDescriptor);
if (methodNode != null)
{
string parameterName = reflectedParameterDescriptor.ParameterInfo.Name;
XPathNavigator parameterNode = methodNode.SelectSingleNode(String.Format(CultureInfo.InvariantCulture, ParameterExpression, parameterName));
if (parameterNode != null)
{
return parameterNode.Value.Trim();
}
}
}
}
return null;
}
and CustomParameterInfo class should keep property info as well:
internal class CustomParameterInfo : ParameterInfo
{
public PropertyInfo Prop { get; private set; }
public CustomParameterInfo(PropertyInfo prop)
{
Prop = prop;
base.NameImpl = prop.Name;
}
}
This is currently not supported out of the box. Following bug is kind of related to that:
http://aspnetwebstack.codeplex.com/workitem/877

RavenDB Spatial index for LineString

How do I create a RavenDB Spatial Index for LineString geo data ?
I am trying to create a spatial index for LINESTRING of geo data, But search query does not return any data.
Please use following testcase as reference, since I am new to RavenDb I am not sure my search query is correct or bug on RavenDB
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
using Raven.Abstractions.Indexing;
using Raven.Client;
using Raven.Client.Embedded;
using Raven.Client.Indexes;
namespace GeoDataLoading.Test
{
[TestFixture]
public class SpatialTest
{
public class GeoDocument
{
public string WKT { get; set; }
}
public class GeoIndex : AbstractIndexCreationTask<GeoDocument>
{
public GeoIndex()
{
Map = docs => from doc in docs
select new {_ = SpatialGenerate("WKT", doc.WKT, SpatialSearchStrategy.GeohashPrefixTree)};
}
}
[Test]
public void LineStringsShouldNearest()
{
using (var store = new EmbeddableDocumentStore {RunInMemory = true})
{
store.Initialize();
store.ExecuteIndex(new GeoIndex());
using (IDocumentSession session = store.OpenSession())
{
session.Store(new GeoDocument
{
WKT =
"LINESTRING (-0.20854 51.80315, -0.20811 51.80395, -0.20811 51.80402, -0.20814 51.80407, -0.20823 51.80419, -0.20888 51.80435, -0.20978 51.80455, -0.21033 51.80463, -0.21088 51.80467, -0.2116 51.80463, -0.21199 51.80457, -0.21246 51.80453, -0.2131 51.80448, -0.21351 51.80442, -0.2143 51.80433, -0.21436 51.80372, -0.21454 51.80321, -0.21468 51.80295)"
});
session.SaveChanges();
}
using (IDocumentSession session = store.OpenSession())
{
List<GeoDocument> result = session.Advanced.LuceneQuery<GeoDocument>("GeoIndex")
.WaitForNonStaleResults()
.WithinRadiusOf(1.2, -0.20854f, 51.80315f)
.SortByDistance()
.ToList();
Assert.IsTrue(result.Count > 0);
}
}
}
}
}
public class YourDocumentType_SpatialIndex : AbstractIndexCreationTask<YourDocumentType>
{
public SpatialIndex()
{
Map = documents => from document in documents
select new
{
document.LinkId,
_ = SpatialGenerate(fieldName: "Geometry", shapeWKT: document.Geometry, strategy: SpatialSearchStrategy.GeohashPrefixTree, maxTreeLevel: 12)
};
}
}
Fair warning that I have not tested this.