We have a couple of models that override the name via JsonProperty, but this causes an issue when we get validation errors through ModelState. For example:
class MyModel
{
[JsonProperty("id")]
[Required]
public string MyModelId {get;set;}
}
class MyModelController
{
public IHttpActionResult Post([FromBody] MyModel model)
{
if (!ModelState.IsValid)
{
return HttpBadRequest(ModelState);
}
/* etc... */
}
}
The above Post will return the error The MyModelId field is required. which isn't accurate. We'd like this to say The id field is required.. We've attempted using [DataMember(Name="id")] but get the same result.
Question 1: Is there a way we can get ModelState errors to show the JSON property name rather than the C# property name aside from providing our own error messages on every [Required] attribute?
-- Update --
I've been playing around with this and found a "do-it-yourself" method for re-creating the error messages using custom property names. I'm really hoping there's a built-in way to do this, but this seems to do the job...
https://gist.github.com/Blackbaud-JasonTremper/b64dc6ddb460afa1698daa6d075857e4
Question 2: Can ModelState.Key be assumed to match the <parameterName>.<reflectedProperty> syntax or are there cases where this might not be true?
Question 3: Is there an easier way to determine what the JSON parameter name is expected to be rather than searching via reflection on [DataMember] or [JsonProperty] attributes?
Did you try using DisplayName attribute?
displayname attribute vs display attribute
Also, you can assign an error message to [Required] attribute.
[Required(ErrorMessage = "Name is required")]
I also faced this problem, I modified some code from your link to fit my WebAPI. modelState will also store the old key which is the variable name of the model, plus the Json Property names.
First, create the filter ValidateModelStateFilter
Add [ValidateModelStateFilter] above controller method
The filter source code:
public class ValidateModelStateFilter : ActionFilterAttribute
{
public override void OnActionExecuting(HttpActionContext actionContext)
{
var descriptor = actionContext.ActionDescriptor;
var modelState = actionContext.ModelState;
if (descriptor != null)
{
var parameters = descriptor.GetParameters();
var subParameterIssues = modelState.Keys
.Where(s => s.Contains("."))
.Where(s => modelState[s].Errors.Any())
.GroupBy(s => s.Substring(0, s.IndexOf('.')))
.ToDictionary(g => g.Key, g => g.ToArray());
foreach (var parameter in parameters)
{
var argument = actionContext.ActionArguments[parameter.ParameterName];
if (subParameterIssues.ContainsKey(parameter.ParameterName))
{
var subProperties = subParameterIssues[parameter.ParameterName];
foreach (var subProperty in subProperties)
{
var propName = subProperty.Substring(subProperty.IndexOf('.') + 1);
var property = parameter.ParameterType.GetProperty(propName);
var validationAttributes = property.GetCustomAttributes(typeof(ValidationAttribute), true);
var value = property.GetValue(argument);
modelState[subProperty].Errors.Clear();
foreach (var validationAttribute in validationAttributes)
{
var attr = (ValidationAttribute)validationAttribute;
if (!attr.IsValid(value))
{
var parameterName = GetParameterName(property);
// modelState.AddModelError(subProperty, attr.FormatErrorMessage(parameterName));
modelState.AddModelError(parameterName, attr.FormatErrorMessage(parameterName));
}
}
}
}
}
}
}
private string GetParameterName(PropertyInfo property)
{
var dataMemberAttribute = property.GetCustomAttributes<DataMemberAttribute>().FirstOrDefault();
if (dataMemberAttribute?.Name != null)
{
return dataMemberAttribute.Name;
}
var jsonProperty = property.GetCustomAttributes<JsonPropertyAttribute>().FirstOrDefault();
if (jsonProperty?.PropertyName != null)
{
return jsonProperty.PropertyName;
}
return property.Name;
}
}
You can access the parameter type, get the json name, and then replace the property name with the json name. Something like this:
var invalidParameters = (from m in actionContext.ModelState
where m.Value.Errors.Count > 0
select new InvalidParameter
{
ParameterName = m.Key,
ConstraintViolations = (from msg in m.Value.Errors select msg.ErrorMessage).ToArray()
}).AsEnumerable().ToArray();
if (actionContext.ActionDescriptor.Parameters.Count == 1)
{
var nameMapper = new Dictionary<string, string>();
foreach (var property in actionContext.ActionDescriptor.Parameters[0].ParameterType.GetProperties())
{
object[] attributes = property.GetCustomAttributes(typeof(JsonPropertyNameAttribute), false);
if (attributes.Length != 1) continue;
nameMapper.Add(property.Name, ((JsonPropertyNameAttribute) attributes[0]).Name);
}
var modifiedInvalidParameters = new List<InvalidParameter>();
foreach (var invalidParameter in invalidParameters)
{
if(invalidParameter.ParameterName != null && nameMapper.TryGetValue(invalidParameter.ParameterName, out var mappedName))
{
var modifiedConstraintViolations = new List<string>();
foreach (var constraintViolation in invalidParameter.ConstraintViolations ?? Enumerable.Empty<string>())
{
modifiedConstraintViolations.Add(constraintViolation.Replace(invalidParameter.ParameterName, mappedName));
}
modifiedInvalidParameters.Add(new InvalidParameter
{
ParameterName = mappedName,
ConstraintViolations = modifiedConstraintViolations.ToArray()
});
}
else
{
modifiedInvalidParameters.Add(invalidParameter);
}
}
invalidParameters = modifiedInvalidParameters.ToArray();
}
public struct InvalidParameter
{
[JsonPropertyName("parameter_name")]
public string? ParameterName { get; set; }
[JsonPropertyName("constraint_violations")]
public string[]? ConstraintViolations { get; set; }
}
Related
In Asp.Net you can automatically parse request data into an input model / API contract using for example the attributes FromBody, FromQuery, and FromRoute. I want to execute this behavior myself. Let me explain.
I want to have a custom policy requirement based on a combination of data passed to the requirement and the target entity which is passed inside the request data. But this target entity id can be in different locations. Usually the body, but for example the route or the query when using HttpGet. So I thought about putting this information about the location above the controller endpoint using an attribute. The following pseudo-code is based on the guess that I need the BindingSource.
I would create API contracts using an interface defining the location of the target id.
public interface ITargetEntityContract {
public string TargetEntityId { get; set; }
}
public class ExampleRequest : ITargetEntityContract {
public string TargetEntityId { get; set; } = default!;
public string SomeOtherData { get; set; } = default!;
}
Then I would create an attribute to define the location:
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class TargetEntityLocationAttribute : Attribute {
public Type ContractType { get; }
public BindingSource BindingSource { get; }
public TargetEntityLocationAttribute(Type contractType, BindingSource bindingSource) {
if (!typeof(ITargetEntityContract).IsAssignableFrom(contractType))
throw new Exception("Contract has to implement the interface ITargetEntityContract");
this.ContractType = contractType;
this.BindingSource = bindingSource;
}
}
And you would apply this onto a controller endpoint the following way:
[TargetEntityLocation(typeof(ExampleRequest), BindingSource.Body)]
public async Task<IActionResult> SomeEndpointAsync([FromBody] ExampleRequest requestData) {
}
Within the IAuthorizationHandler I would use these classes the following way:
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, ExampleAuthorizationRequirement requirement) {
var endpoint = this._httpContextAccessor.HttpContext!.GetEndpoint();
if (endpoint is null)
throw new Exception("Some error");
var targetEntityLocation = endpoint.MetaData.GetMetaData<TargetEntityLocationAttribute>();
if (targetEntityLocation is null)
throw new Exception("some error");
var targetEntityModel = SomeAlmightyParser.ParseFromSource(this._httpContextAccessor.HttpContext!, targetEntityLocation.ContractType, targetEntityLocation.BindingSource) as ITargetEntityContract;
// do something with targetEntityModel.TargetEntityId
}
Is there a way to parse the request data of the HttpContext into the given model based on the data location?
If the source of the data is RequestBody,you could read the stream with StreamReader and deserialize the string you get;
If the source of the data is RequestQuery,you could map the key-value pairs to your target object with reflection
For example,I tried as below:
//read obj from requestbody
using var reader = new StreamReader(context.Request.Body);
var bodystr = reader.ReadToEndAsync().Result;
if (bodystr != "")
{
var obj1 = JsonSerializer.Deserialize(bodystr, typeof(ExampleRequest), new JsonSerializerOptions() { });
}
//read simple obj from query
var querycollection = context.Request.Query;
var type = typeof(ExampleRequest);
var properties = type.GetProperties();
object obj = Activator.CreateInstance(type);
foreach (var propinfo in properties)
{
var propname = propinfo.Name;
if (querycollection.ContainsKey(propname))
{
var proptype = propinfo.PropertyType;
propinfo.SetValue(obj, Convert.ChangeType(querycollection[propname].ToString(), proptype),null);
}
}
var obj2 = obj as ExampleRequest;
Result:
I have a public Dictionary<string, PostRenewalActionJobs> Jobs to store some actions I would like to trigger for specific accounts, the key of this dictionary being the account name.
public class PostRenewalActionJobs
{
public List<AlterDatabaseLinkJob> AlterDataBaseLink { get; set; }
public DatabaseConnectionCheckJob DatabaseConnectionCheck { get; set; }
public UnlockDatabaseAccountJob UnlockDatabaseAccount { get; set; }
public LinuxConnectionCheckJob LinuxConnectionCheck { get; set; }
public WindowsConnectionCheckJob WindowsConnectionCheck { get; set; }
public ReplacePasswordInFileJob ReplacePasswordInFile { get; set; }
}
The properties of PostRenewalActionJobs type (AlterDataBaseLink, DatabaseConnectionCheck, etc) can be defined for a specific account or for all accounts by using * as key in the dictionary:
By using below method I am able to retrieve the jobs for an account (if exists) or the general jobs:
public PostRenewalActionJobs GetJobsForAccount(string accountName)
{
return Jobs.ContainsKey(accountName) ? Jobs[accountName] : Jobs["*"];
}
I would like to have a dynamic way of getting a job from the all accounts object ("*") if the one from the specific account is null.
Something like below but whit out repeating the same code for all job types and also a solution that should work when new job types are introduced.
var dbConCheckJob = GetJobsForAccount("someAccount").AlterDataBaseLink;
if(dbConCheckJob == null || !dbConCheckJob.Any())
{
dbConCheckJob = GetJobsForAccount("*").AlterDataBaseLink
}
I was thinking to use some reflection, but I am not sure how to do it.
You don't need to use reflection. You can already determine whether to get the specific jobs for an account or the generic ones, you could then use a Func to get the job you want:
public TJob GetPostJobForAccount<TJob>(string accountName,
Func<PostRenewalActionJobs, TJob> jobSelector) where TJob : JobBase
{
var genericJobs = Jobs["*"];
var accountJobs = Jobs.ContainsKey(accountName) ? Jobs[accountName] : genericJobs;
// Account might be defined but without any job of the given type
// hence selecting from the defaults if need be
return jobSelector(accountJobs) ?? jobSelector(genericJobs);
}
var bobJob = GetPostJobForAccount("bob", x => x.WindowsConnectionCheck);
var aliceJob = GetPostJobForAccount("alice", x => x.UnlockDatabaseAccount);
I found a way to do it, not sure if there is a better way:
public TJob GetPostJobForAccount<TJob>(string accountName)
{
Type type = typeof(PostRenewalActionJobs);
var accountJobs = Jobs[accountName];
var generalJobs = Jobs["*"];
foreach (var item in type.GetProperties())
{
var itemType = item.PropertyType;
var currentType = typeof(TJob);
if (itemType != currentType)
{
continue;
}
var output = (TJob)accountJobs?.GetType()?.GetProperty(item.Name)?.GetValue(accountJobs, null);
if (output is null)
{
output = (TJob)accountJobs?.GetType()?.GetProperty(item.Name)?.GetValue(generalJobs, null);
}
return output;
}
return default;
}
I am completely new to EpiServer and this has been killing me for days :(
I am looking for a simple way to convert a page and all it's descendents to a JSON tree.
I have got this far:
public class MyPageController : PageController<MyPage>
{
public string Index(MyPage currentPage)
{
var output = new ExpandoObject();
var outputDict = output as IDictionary<string, object>;
var pageRouteHelper = ServiceLocator.Current.GetInstance<EPiServer.Web.Routing.PageRouteHelper>();
var pageReference = pageRouteHelper.PageLink;
var children = DataFactory.Instance.GetChildren(pageReference);
var toOutput = new { };
foreach (PageData page in children)
{
outputDict[page.PageName] = GetAllContentProperties(page, new Dictionary<string, object>());
}
return outputDict.ToJson();
}
public Dictionary<string, object> GetAllContentProperties(IContentData content, Dictionary<string, object> result)
{
foreach (var prop in content.Property)
{
if (prop.IsMetaData) continue;
if (prop.GetType().IsGenericType &&
prop.GetType().GetGenericTypeDefinition() == typeof(PropertyBlock<>))
{
var newStruct = new Dictionary<string, object>();
result.Add(prop.Name, newStruct);
GetAllContentProperties((IContentData)prop, newStruct);
continue;
}
if (prop.Value != null)
result.Add(prop.Name, prop.Value.ToString());
}
return result;
}
}
The problem is, by converting the page structure to Dictionaries, the JsonProperty PropertyName annotations in my pages are lost:
[ContentType(DisplayName = "MySubPage", GroupName = "MNRB", GUID = "dfa8fae6-c35d-4d42-b170-cae3489b9096", Description = "A sub page.")]
public class MySubPage : PageData
{
[Display(Order = 1, Name = "Prop 1")]
[CultureSpecific]
[JsonProperty(PropertyName = "value-1")]
public virtual string Prop1 { get; set; }
[Display(Order = 2, Name = "Prop 2")]
[CultureSpecific]
[JsonProperty(PropertyName = "value-2")]
public virtual string Prop2 { get; set; }
}
This means I get JSON like this:
{
"MyPage": {
"MySubPage": {
"prop1": "...",
"prop2": "..."
}
}
}
Instead of this:
{
"MyPage": {
"MySubPage": {
"value-1": "...",
"value-2": "..."
}
}
}
I know about using custom ContractResolvers for the JSON serialisation, but that will not help me because I need JSON property names that cannot be inferred from the C# property name.
I would also like to be able to set custom JSON property names for the pages themselves.
I really hope that a friendly EpiServer guru can help me out here!
Thanks in advance :)
One of the C# dev's on my project rolled his own solution for this in the end. He used reflection to examine the page tree and built the JSON up from that. Here it is. Hopefully it will help someone else as much as it did me!
using EPiServer;
using EPiServer.Core;
using EPiServer.DataAbstraction;
using EPiServer.DataAnnotations;
using EPiServer.ServiceLocation;
using EPiServer.Web.Mvc;
using Newtonsoft.Json;
using System.Collections.Generic;
using System.Dynamic;
using System.Reflection;
using System;
using System.Runtime.Caching;
using System.Linq;
using Newtonsoft.Json.Linq;
using EPiServer.Framework;
using EPiServer.Framework.Initialization;
namespace NUON.Models.MyCorp
{
public class MyCorpPageController : PageController<MyCorpPage>
{
public string Index(MyCorpPage currentPage)
{
Response.ContentType = "text/json";
// check if the JSON is cached - if so, return it
ObjectCache cache = MemoryCache.Default;
string cachedJSON = cache["myCorpPageJson"] as string;
if (cachedJSON != null)
{
return cachedJSON;
}
var output = new ExpandoObject();
var outputDict = output as IDictionary<string, object>;
var pageRouteHelper = ServiceLocator.Current.GetInstance<EPiServer.Web.Routing.PageRouteHelper>();
var pageReference = pageRouteHelper.PageLink;
var contentLoader = ServiceLocator.Current.GetInstance<IContentLoader>();
var children = contentLoader.GetChildren<PageData>(currentPage.PageLink).OfType<PageData>();
var toOutput = new { };
var jsonResultObject = new JObject();
foreach (PageData page in children)
{
// Name = e.g. BbpbannerProxy . So remove "Proxy" and add the namespace
var classType = Type.GetType("NUON.Models.MyCorp." + page.GetType().Name.Replace("Proxy", string.Empty));
// Only keep the properties from this class, not the inherited properties
jsonResultObject.Add(page.PageName, GetJsonObjectFromType(classType, page));
}
// add to cache
CacheItemPolicy policy = new CacheItemPolicy();
// expire the cache daily although it will be cleared whenever content changes.
policy.AbsoluteExpiration = DateTimeOffset.Now.AddDays(1.0);
cache.Set("myCorpPageJson", jsonResultObject.ToString(), policy);
return jsonResultObject.ToString();
}
[InitializableModule]
[ModuleDependency(typeof(EPiServer.Web.InitializationModule),
typeof(EPiServer.Web.InitializationModule))]
public class EventsInitialization : IInitializableModule
{
public void Initialize(InitializationEngine context)
{
var events = ServiceLocator.Current.GetInstance<IContentEvents>();
events.PublishedContent += PublishedContent;
}
public void Preload(string[] parameters)
{
}
public void Uninitialize(InitializationEngine context)
{
}
private void PublishedContent(object sender, ContentEventArgs e)
{
// Clear the cache because some content has been updated
ObjectCache cache = MemoryCache.Default;
cache.Remove("myCorpPageJson");
}
}
private static JObject GetJsonObjectFromType(Type classType, object obj)
{
var jsonObject = new JObject();
var properties = classType.GetProperties(BindingFlags.Public
| BindingFlags.Instance
| BindingFlags.DeclaredOnly);
foreach (var property in properties)
{
var jsonAttribute = property.GetCustomAttributes(true).FirstOrDefault(a => a is JsonPropertyAttribute);
var propertyName = jsonAttribute == null ? property.Name : ((JsonPropertyAttribute)jsonAttribute).PropertyName;
if (property.PropertyType.BaseType == typeof(BlockData))
jsonObject.Add(propertyName, GetJsonObjectFromType(property.PropertyType, property.GetValue(obj)));
else
{
var propertyValue = property.PropertyType == typeof(XhtmlString) ? property.GetValue(obj)?.ToString() : property.GetValue(obj);
if (property.PropertyType == typeof(string))
{
propertyValue = propertyValue ?? String.Empty;
}
jsonObject.Add(new JProperty(propertyName, propertyValue));
}
}
return jsonObject;
}
}
[ContentType(DisplayName = "MyCorpPage", GroupName = "MyCorp", GUID = "bc91ed7f-d0bf-4281-922d-1c5246cab137", Description = "The main MyCorp page")]
public class MyCorpPage : PageData
{
}
}
Hi I'm looking for the same thing, and until now I find this page and component.
https://josefottosson.se/episerver-contentdata-to-json/
https://github.com/joseftw/JOS.ContentJson
I hope you find it useful
So what i am doing is that i am displaying a list of categories in a page and each category contains a sublist of categories. I have a controller and it is returning the list of categories without the sublist of categories. how can i get the sublist showing from using the same controller.
Controller:
public CourseIndexVw Get(int id)
{
var _types = new ElementTypesService().GetElementModelsForCourseIndex(id, WebSecurity.CurrentUserId);
var _courseIndexbyTypesVw = new CourseSectionsControllerHelper().CourseIndexTypeVw(id);
_courseIndexbyTypesVw.Types = _types.ToList();
var _activeType = _courseIndexbyTypesVw.Types.First();
_courseIndexbyTypesVw.ActiveId = _activeType != null ? _activeType.Id : -1;
return _courseIndexbyTypesVw;
}
GetElementModelsForCourseIndex:
public List<ElementModelForCourseIndex> GetElementModelsForCourseIndex(int elementId, int userId, int depthLevel = 2)
{
List<ElementModelForCourseIndex> TypesName;
ElementType type;
using (var db = DataContextManager.AleStoredProcsContext)
{
TypesName = db.GetElementModelsForCourseIndex<ElementModelForCourseIndex>(elementId, userId, r => new ElementModelForCourseIndex{
Id = ElementsModelsForCourseIndexMap.Id(r),
Identity = ElementsModelsForCourseIndexMap.Identity(r)
}).OrderBy(n=>n.Identity).ToList();
}
foreach (ElementModelForCourseIndex typeContent in TypesName)
{
typeContent.Children = GetElementChildrenModelsForCourseIndex(elementId, userId, type.ModelId, depthLevel);
}
}
GetElementChildrenModelsForCourseIndex:
public List<ElementModelForCourseIndex> GetElementChildrenModelsForCourseIndex(int elementId, int userId, ElementType typeId, int depthLevel = 2)
{
using (var db = DataContextManager.AleStoredProcsContext)
{
return db.GetElementWithCalendarAndPermsByModel<ElementModelForCourseIndex>(elementId, userId, typeId.Id, r => new ElementModelForCourseIndex
{
IdentityName = ElementsModelsForCourseIndexMap.IdentityName(r),
ValueString = ElementsModelsForCourseIndexMap.ValueString(r),
TimeReleased = ElementsModelsForCourseIndexMap.TimeReleased(r),
TimeDue = ElementsModelsForCourseIndexMap.TimeReleased(r)
}).OrderBy(i => i.IdentityName).ToList();
}
}
UPDATE
Current issue is with typeContent.Children = GetElementChildrenModelsForCourseIndex(elementId, userId, type.ModelId, depthLevel); The error i am getting is: the override method has invalid arguments
Any Help is appreciated and if i am missing any information let me know. Thanks!
You can modify your model and add a children property:
public class ElementModelForCourseIndex
{
// *snip* your code
public List<ElementModelForCourseIndex> Children {get; set;}
}
You could either get it within your current GetElementModelsForCourseIndex or use you helper method like this:
public List<ElementModelForCourseIndex> GetElementModelsForCourseIndex(int elementId, int userId, int depthLevel = 2)
{
List<ElementModelForCourseIndex> courses;
using (var db = DataContextManager.AleStoredProcsContext)
{
courses = db.GetElementModelsForCourseIndex<ElementModelForCourseIndex>(elementId, userId, r => new ElementModelForCourseIndex{
Id = ElementsModelsForCourseIndexMap.Id(r),
Identity = ElementsModelsForCourseIndexMap.Identity(r)
}).OrderBy(n=>n.Identity).ToList();
}
for each(ElementModelForCourseIndex course in courses)
{
// here you are filling the Children.
//You need to check if the parameters are the correct ones.
// Since you haven't shown the actual model class, I'm only guessing the parameters
course.Children = GetElementChildrenModelsForCourseIndex(elementId, userId, depthLevel);
}
return courses;
}
I created a custom gridview control and exported it into a dll so I can reuse it. Inside the dll I created a function to get the DataSource, I'm trying to fill a dropdown from there but is failing.
So on my website I have this
public partial class _Management : System.Web.UI.Page
{
public class _ManagementHelper
{
public int ID;
public string CompanyName;
public string ResourceName;
}
protected void Page_Load(object sender, EventArgs e)
{
ucGridViewEx.DataSource = ucGridViewEx_Source();
ucGridViewEx.DataBind();
}
private List<dynamic> ucGridViewEx_Source()
{
var source = dl.ComapniesResources.Select(x => new _ManagementHelper
{
ID = x.ResourceID,
CompanyName = x.Supplier1.SupplierName,
ResourceName = x.Name
});
return ucGridViewEx.GridViewExDataSource(source);
}
Then the custom control inside the dll have this relevant code
public List<dynamic> GridViewExDataSource<T>(IQueryable<T> query)
{
foreach (var column in this.Columns)
{
var gridViewExColumn = column as ColumnEx;
if (gridViewExColumn != null
&& gridViewExColumn.SearchType == SearchTypeEnum.DropDownList)
{
gridViewExColumn.DropDownDataSource = query.GetDropDownDataSource(gridViewExColumn.DataField);
}
}
return ((IQueryable<dynamic>)query).ToList<dynamic>();
}
Function GetDropDownDataSource() is inside another extension class inside the same dll as the gridview
internal static List<ListItem> GetDropDownDataSource<T>(this IQueryable<T> query,
string dataField)
{
var ddlSource = new List<ListItem>();
// x =>
var xParameter = Expression.Parameter(typeof(T), "x");
// x.Property
var propery = typeof(T).GetProperty(dataField);
// x => x.Property
var columnLambda = Expression.Lambda(Expression.Property(xParameter, propery), xParameter);
return ddlSource;
}
Code fails in this where I'm assingning the value to columnLambda because property is null, not because it does not exist (it does) because is not getting any property. I tried with GetProperties() and is not returning anything.
Is curious than this is happening since I moved to the DataSource to select into _ManagementHelper. I was using a dynamic ( Select(x => new {}) ) on ucGridViewEx_Source() before and it worked perfectly. Please don't provide the solution to keep using the dynamic because I need to allow both types, with dynamic and using custom objects.
_ManagementHelper has no property. It just contains three fields (as far as you told us). So GetPrperty returns nothing. Change the members of _ManagementHelper to properties:
public class _ManagementHelper
{
public int ID { get; set; }
public string CompanyName { get; set; }
public string ResourceName { get; set; }
}
I see one bug --
var source = dl.ComapniesResources.Select(x => new _ManagementHelper
{
// ResourceID = x.ResourceID, this was the old code
ID = x.ResourceID, // fixed code
CompanyName = x.Supplier1.SupplierName,
ResourceName = x.Name
});
also, where is ListItem defined?