I have written one Caching class but i am confused how do i use it in application in MVC ?
using System;
using System.Configuration;
using System.Runtime.Caching;
namespace ABC
{
public class InMemoryCache : ICache
{
protected ObjectCache Cache
{
get
{
return MemoryCache.Default;
}
}
/// Insert value into the cache using key, Value pairs
public void Add<T>(T o, string key)
{
if (o == null)
return;
var policy = new CacheItemPolicy();
policy.AbsoluteExpiration = DateTime.Now.AddMinutes(Convert.ToInt32(ConfigurationManager.AppSettings["CacheExpirationTime"]));
Cache.Add(new CacheItem(key, o), policy);
}
public void Update<T>(T o, string key)
{
if (Cache.Contains(key))
{
Cache[key] = o;
}
else
{
Add(o, key);
}
}
/// Remove item from cache
public void Remove(string key)
{
Cache.Remove(key);
}
/// Check for item in cache
public bool Exists(string key)
{
return Cache[key] != null;
}
public void Clear()
{
foreach (var item in Cache)
Remove(item.Key);
}
/// Retrieve cached item.Default(T) if item doesn't exist
public bool Get<T>(string key, out T value)
{
try
{
if (!Exists(key))
{
value = default(T);
return false;
}
value = (T)Cache[key];
}
catch
{
value = default(T);
return false;
}
return true;
}
public T Get<T>(string key)
{
try
{
return (T)Cache[key];
}
catch
{
return default(T);
}
}
}
}
I have BaseController where i have written OnAuthentication()
//How can i use Caching here as it hits on every click made in application
protected override void OnAuthentication(AuthenticationContext filterContext)
{
Check.NotEmpty(SsoId, "Unable to determine your AmexWeb logon identity. Please contact the system administrator.");
var requestingUser = new RBACUser(SsoId, _sprocService);
//How can i use Caching here as it hits on every click made in application
UserPermissions = requestingUser.UserPermissions;
PermissionsMaster = requestingUser.PermissionsMaster;
var user = LoggedInUser = requestingUser.User;
if (user == null)
{
if (HttpContext.Session != null)
{
HttpContext.Session["User"] = null;
HttpContext.Session["UserName"] = null;
}
}
else
{
if (HttpContext.Session != null)
{
HttpContext.Session["User"] = user.ADSId;
HttpContext.Session["UserName"] = user.FullName;
}
_userId = user.UserId;
_regionCode = user.Segment.RegionCode;
}
}
You could try to implement caching in OnActionExecuting() method so it would work only once per action.
public class BaseController : Controller
{
protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
// Work with a cache here
base.OnActionExecuting(filterContext);
}
}
Related
Httpcontext.current.session becoming null in local code, session not expired still in active and I didn't clear session manually when click on button controller will receive post request and after authenticating user setting user details in httpcontext.current.session, after setting if I send another post request all values are becoming null
internal class AspDotNet : Base
{
internal AspDotNet(String key)
: base(key)
{
}
internal override void Set(Object value)
{
this.Value = value;
HttpContext.Current.Session[base.Key] = value;
}
internal override Object Get()
{
this.Value = HttpContext.Current.Session[base.Key];
return this.Value;
}
internal override void Remove()
{
HttpContext.Current.Session.Remove(base.Key);
}
}
}
can you try like this. I believe static class will work for you.
public static class SessionPersiter
{
public static int? UserID
{
get
{
if (HttpContext.Current == null)
return (Int32?)null;
var sessionData = HttpContext.Current.Session[CommonConstants.UserIDSessionKey];
if (sessionData == null)
return (Int32?)null;
return (int)sessionData;
}
set
{
HttpContext.Current.Session[CommonConstants.UserIDSessionKey] = value;
}
}
public static UserDTO AuthenticatedUser
{
get
{
if (HttpContext.Current == null)
return null;
var sessionData = HttpContext.Current.Session[CommonConstants.AuthenticatedUserKey];
if (sessionData == null)
return null;
return (UserDTO)sessionData;
}
set
{
HttpContext.Current.Session[CommonConstants.AuthenticatedUserKey] = value;
}
}
public static long? LastLoginID
{
get
{
if (HttpContext.Current == null)
return (long?)null;
var sessionData = HttpContext.Current.Session[CommonConstants.LastLoginIDSessionKey];
if (sessionData == null)
return (long?)null;
return (long)sessionData;
}
set
{
HttpContext.Current.Session[CommonConstants.LastLoginIDSessionKey] = value;
}
}
}
and call like this
var userToken = SessionPersiter.AuthenticatedUser.Token;
I have the following protoc3 message:
message LocalizedString {
map<string, string> translations = 1
}
When compiled into C#, I get the following autogenerated code:
using pb = global::Google.Protobuf;
using pbc = global::Google.Protobuf.Collections;
using pbr = global::Google.Protobuf.Reflection;
using scg = global::System.Collections.Generic;
namespace PKIo {
/// <summary>Holder for reflection information generated from io/common/localization.proto</summary>
public static partial class LocalizationReflection {
#region Descriptor
/// <summary>File descriptor for io/common/localization.proto</summary>
public static pbr::FileDescriptor Descriptor {
get { return descriptor; }
}
private static pbr::FileDescriptor descriptor;
static LocalizationReflection() {
byte[] descriptorData = global::System.Convert.FromBase64String(
string.Concat(
"Chxpby9jb21tb24vbG9jYWxpemF0aW9uLnByb3RvEgJpbyKDAQoPTG9jYWxp",
"emVkU3RyaW5nEjsKDHRyYW5zbGF0aW9ucxgBIAMoCzIlLmlvLkxvY2FsaXpl",
"ZFN0cmluZy5UcmFuc2xhdGlvbnNFbnRyeRozChFUcmFuc2xhdGlvbnNFbnRy",
"eRILCgNrZXkYASABKAkSDQoFdmFsdWUYAiABKAk6AjgBQi1aJHN0YXNoLnBh",
"c3NraXQuY29tL2lvL21vZGVsL3Nkay9nby9pb6oCBFBLSW9iBnByb3RvMw=="));
descriptor = pbr::FileDescriptor.FromGeneratedCode(descriptorData,
new pbr::FileDescriptor[] { },
new pbr::GeneratedClrTypeInfo(null, new pbr::GeneratedClrTypeInfo[] {
new pbr::GeneratedClrTypeInfo(typeof(global::PKIo.LocalizedString), global::PKIo.LocalizedString.Parser, new[]{ "Translations" }, null, null, new pbr::GeneratedClrTypeInfo[] { null, })
}));
}
#endregion
}
#region Messages
/// <summary>
/// Localized strings are optionally used to provide translated values for each of supported language.
/// </summary>
public sealed partial class LocalizedString : pb::IMessage<LocalizedString> {
private static readonly pb::MessageParser<LocalizedString> _parser = new pb::MessageParser<LocalizedString>(() => new LocalizedString());
private pb::UnknownFieldSet _unknownFields;
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
public static pb::MessageParser<LocalizedString> Parser { get { return _parser; } }
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
public static pbr::MessageDescriptor Descriptor {
get { return global::PKIo.LocalizationReflection.Descriptor.MessageTypes[0]; }
}
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
pbr::MessageDescriptor pb::IMessage.Descriptor {
get { return Descriptor; }
}
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
public LocalizedString() {
OnConstruction();
}
partial void OnConstruction();
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
public LocalizedString(LocalizedString other) : this() {
translations_ = other.translations_.Clone();
_unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields);
}
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
public LocalizedString Clone() {
return new LocalizedString(this);
}
/// <summary>Field number for the "translations" field.</summary>
public const int TranslationsFieldNumber = 1;
private static readonly pbc::MapField<string, string>.Codec _map_translations_codec
= new pbc::MapField<string, string>.Codec(pb::FieldCodec.ForString(10), pb::FieldCodec.ForString(18), 10);
private readonly pbc::MapField<string, string> translations_ = new pbc::MapField<string, string>();
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
public pbc::MapField<string, string> Translations {
get { return translations_; }
}
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
public override bool Equals(object other) {
return Equals(other as LocalizedString);
}
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
public bool Equals(LocalizedString other) {
if (ReferenceEquals(other, null)) {
return false;
}
if (ReferenceEquals(other, this)) {
return true;
}
if (!Translations.Equals(other.Translations)) return false;
return Equals(_unknownFields, other._unknownFields);
}
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
public override int GetHashCode() {
int hash = 1;
hash ^= Translations.GetHashCode();
if (_unknownFields != null) {
hash ^= _unknownFields.GetHashCode();
}
return hash;
}
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
public override string ToString() {
return pb::JsonFormatter.ToDiagnosticString(this);
}
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
public void WriteTo(pb::CodedOutputStream output) {
translations_.WriteTo(output, _map_translations_codec);
if (_unknownFields != null) {
_unknownFields.WriteTo(output);
}
}
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
public int CalculateSize() {
int size = 0;
size += translations_.CalculateSize(_map_translations_codec);
if (_unknownFields != null) {
size += _unknownFields.CalculateSize();
}
return size;
}
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
public void MergeFrom(LocalizedString other) {
if (other == null) {
return;
}
translations_.Add(other.translations_);
_unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields);
}
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
public void MergeFrom(pb::CodedInputStream input) {
uint tag;
while ((tag = input.ReadTag()) != 0) {
switch(tag) {
default:
_unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input);
break;
case 10: {
translations_.AddEntriesFrom(input, _map_translations_codec);
break;
}
}
}
}
}
#endregion
}
I have another message that has a LocalizedString property that I am using for as an input to an rpc function, but I cannot figure out how to set the properties for Translations. In the generated code, Translations is marked as read only.
How does one construct a protobuf message object that contains a map like this in C#?
So after digging through the documentation, I found that Google.Protobuf.Collections.MapField< TKey, TValue > has getters and setters.
The values can by either passing a key, value pair or a dictionary to the Add method of the map.
var localizedName = new LocalizedString();
localizedName.Translations.Add(new Dictionary<string, string>(){
{"ES","Hola"},
{"FR","Bonjour"},
{"JA","こんにちは"},
{"TH","สวัสดี"},
});
localizedName.Translations.Add("ZH_HANS", "你好");
To retrive a value there is a TryGetValue method:
var translation = "";
localizedName.Translations.TryGetValue("TH", out translation);
// translation == "สวัสดี"
In the method MergeFrom, it is giving option to add values in translations in line translations_.Add(other.translations_); :
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
public void MergeFrom(LocalizedString other) {
if (other == null) {
return;
}
translations_.Add(other.translations_);
_unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields);
}
To answer "How does one construct a protobuf message object that contains a map like this in C#", Please refer:
https://developers.google.com/protocol-buffers/docs/reference/csharp-generated
https://developers.google.com/protocol-buffers/docs/reference/csharp/class/google/protobuf/collections/map-field-t-key-t-value-
Also there is pull request in github that provides "Proto3 map support for C#" to get the idea how it is implemented:
https://github.com/protocolbuffers/protobuf/pull/543
Also refer these to check if it helps:
Dictionary in protocol buffers
How does Protobuf-net support for Dictionary/KeyValuePair works?
this is the error:
System.InvalidOperationException: Collection was modified; enumeration operation may not execute.
at System.ThrowHelper.ThrowInvalidOperationException(ExceptionResource resource)
at System.Collections.Generic.List1.Enumerator.MoveNextRare()
at System.Collections.Generic.List1.Enumerator.MoveNext()
at System.Linq.Enumerable.WhereListIterator1.MoveNext()
at System.Linq.Enumerable.Count[TSource](IEnumerable1 source)
Blockquote
I use the static Dictionary for web api
this is my class that i use for my web api :
public class UsersSecureProvider
{
public static ConcurrentDictionary<short, List<UserSecure>> _Users = new ConcurrentDictionary<short, List<UserSecure>>();
public bool Add(short Group, UserSecure Message)
{
try
{
var GetList = GetByKey(Group);
if (GetList != null)
{
GetList.Add(Message);
return Update(Group, GetList, GetList);
}
else
{
GetList = new List<UserSecure>();
GetList.Add(Message);
return Add(Group, GetList);
}
}
catch { }
return false;
}
private bool Add(short key, List<UserSecure> SendUser)
{
return _Users.TryAdd(key, SendUser);
}
public bool Remove(short Key)
{
List<UserSecure> listremove;
return _Users.TryRemove(Key, out listremove);
}
public List<UserSecure> GetByKey(short Group)
{
var listView = new List<UserSecure>();
if (_Users != null)
{
var getList = _Users.TryGetValue(Group, out listView);
}
return listView;
}
public bool Update(short Group, List<UserSecure> oldlist, List<UserSecure> newlist)
{
return _Users.TryUpdate(Group, newlist, oldlist);
}
public void Clear()
{
_Users.Clear();
}
public ConcurrentDictionary<short, List<UserSecure>> GetAll()
{
return _Users;
}
public bool UpdateListByUser(short Group, List<UserSecure> newlist)
{
var OldList = GetByKey(Group);
return _Users.TryUpdate(Group, newlist, OldList);
}
}
And I call the class
var _providers = new UsersSecureProvider();
List<UserSecure> GetAll = _providers.GetByKey(1);
if (GetAll != null && GetAll.Any() && GetAll.Where(w => w.UserID == UserID && w.Key == UniqueSecure).Count() > 0)
{
result = true;
}
else
{
_providers.Add(1, new UserSecure { UserID = UserID, Key = UniqueSecure });
}
why do i receive this error exception?
thank you.
This:
List<UserSecure> GetAll = _providers.GetByKey(1);
Returns a reference to the underlying collection. That same reference to a list which is probably being modified via one of the other WebAPI actions you have. You cannot both enumerate and modify the List<T>.
Instead, create a new List<T> and enumerate it:
List<UserSecure> GetAll = _providers.GetByKey(1).ToList();
If your application is multi threading it is recomanded to use semaphore. For example
private static object _sync = new object();
public List<UserSecure> GetByKey(short Group)
{
lock(_sync)
{
var listView = new List<UserSecure>();
if (_Users != null)
{
var getList = _Users.TryGetValue(Group, out listView);
}
return listView;
}
}
ASP.NET MVC4 application uses modified WebMatrix Dynamicrecord to get dynamic data from ADO.NET and show it in a WebGrid.
Running application causes strange exception
Invalid attempt to read when no data is present
in method
private object GetNonNullValue(int i)
at line
var value = Record[i];
Using foreach as shown in comment does not work in Mono as discussed in https://github.com/npgsql/npgsql/issues/295 . So application uses while but while does not work in Windows also.
How to get dynamic data in while loop ?
Whole solution is available at http://wikisend.com/download/360760/invalidattemptoreadwhennodataispresent.zip
Controller:
using Eeva.Business;
using Eeva.Erp.ViewModels;
using Npgsql;
using System.Collections.Generic;
using System.Data;
using System.Data.Common;
using System.Web.Mvc;
namespace Eeva.Erp.Controllers
{
public class ReportController : Controller
{
public ActionResult Test()
{
var data = TestData();
return View("ReportData", new ReportDataViewModel(data, ""));
}
IEnumerable<dynamic> TestData()
{
using (var connection = new NpgsqlConnection(ConnectionString()))
{
connection.Open();
DbCommand command = (DbCommand)connection.CreateCommand();
command.CommandText = "select 'A' union select 'B'";
using (command)
{
using (DbDataReader reader = command.ExecuteReader())
{
IEnumerable<string> columnNames = null;
while (reader.Read())
{
if (columnNames == null)
columnNames = GetColumnNames(reader);
yield return new EevaDynamicRecord(columnNames, reader);
}
//foreach (DbDataRecord record in reader)
//{
// if (columnNames == null)
// columnNames = GetColumnNames(record);
// yield return new EevaDynamicRecord(columnNames, record);
//}
}
}
}
}
static IEnumerable<string> GetColumnNames(IDataRecord record)
{
// Get all of the column names for this query
for (int i = 0; i < record.FieldCount; i++)
yield return record.GetName(i);
}
static string ConnectionString()
{
return new NpgsqlConnectionStringBuilder()
{
Host = "localhost",
UserName = "postgres",
}.ConnectionString;
}
}
}
ViewModel:
using System.Collections.Generic;
using System.Web.Mvc;
using Eeva.Business;
namespace Eeva.Erp.ViewModels
{
public class ReportDataViewModel
{
public IEnumerable<dynamic> Rows { get; set; }
public string Source;
public ReportDataViewModel(IEnumerable<dynamic> rows, string source)
{
Rows = rows;
Source = source;
}
}
}
View:
#model Eeva.Erp.ViewModels.ReportDataViewModel
#using System.Web.Helpers
#{ Layout = null;
var gd = new WebGrid(source: Model.Rows );
}
<!DOCTYPE html>
<html>
<head></head>
<body>
#gd.GetHtml()
</body>
</html>
Dynamicrecord is used from MVC4 source code with modifications:
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Dynamic;
using System.Globalization;
using System.Linq;
using System.Text;
using WebMatrix.Data.Resources;
namespace Eeva.Business
{
public sealed class EevaDynamicRecord : DynamicObject, ICustomTypeDescriptor
{
public EevaDynamicRecord(IEnumerable<string> columnNames, IDataRecord record)
{
Debug.Assert(record != null, "record should not be null");
Debug.Assert(columnNames != null, "columnNames should not be null");
Columns = columnNames.ToList();
Record = record;
}
public IList<string> Columns { get; private set; }
private IDataRecord Record { get; set; }
public object this[string name]
{
get
{
for (int i = 0; i < Record.FieldCount; i++)
{
string normname = Record.GetName(i);
if (normname.Equals(name, StringComparison.InvariantCultureIgnoreCase))
return GetNonNullValue(i);
}
throw new InvalidOperationException("No column " + name);
}
}
public object this[int index]
{
get
{
return GetNonNullValue(index); // GetValue(Record[index]);
}
}
public string Field(int fldNo)
{
return Record.GetName(fldNo).ToUpperInvariant();
}
public override bool TryGetMember(GetMemberBinder binder, out object result)
{
result = this[binder.Name];
return true;
}
private object GetNonNullValue(int i)
{
var value = Record[i];
if (DBNull.Value == value || value == null)
{
var tt = Record.GetFieldType(i).Name;
switch (tt)
{
case "Decimal":
case "Int32":
case "Double":
return 0;
case "String":
return "";
case "DateTime":
return null;
case "Boolean":
// kui seda pole, siis demos lao kartoteek kartoteegi kaart annab vea:
return false;
}
return null;
}
if (value is decimal? || value is decimal)
return Convert.ChangeType(value, typeof(double));
if (value is string)
return value.ToString().TrimEnd();
return value;
}
public override IEnumerable<string> GetDynamicMemberNames()
{
return Columns;
}
private void VerifyColumn(string name)
{
// REVIEW: Perf
if (!Columns.Contains(name, StringComparer.OrdinalIgnoreCase))
{
throw new InvalidOperationException(
String.Format(CultureInfo.CurrentCulture,
"Invalid Column Name " + name));
}
}
AttributeCollection ICustomTypeDescriptor.GetAttributes()
{
return AttributeCollection.Empty;
}
string ICustomTypeDescriptor.GetClassName()
{
return null;
}
string ICustomTypeDescriptor.GetComponentName()
{
return null;
}
TypeConverter ICustomTypeDescriptor.GetConverter()
{
return null;
}
EventDescriptor ICustomTypeDescriptor.GetDefaultEvent()
{
return null;
}
PropertyDescriptor ICustomTypeDescriptor.GetDefaultProperty()
{
return null;
}
object ICustomTypeDescriptor.GetEditor(Type editorBaseType)
{
return null;
}
EventDescriptorCollection ICustomTypeDescriptor.GetEvents(Attribute[] attributes)
{
return EventDescriptorCollection.Empty;
}
EventDescriptorCollection ICustomTypeDescriptor.GetEvents()
{
return EventDescriptorCollection.Empty;
}
PropertyDescriptorCollection ICustomTypeDescriptor.GetProperties(Attribute[] attributes)
{
return ((ICustomTypeDescriptor)this).GetProperties();
}
PropertyDescriptorCollection ICustomTypeDescriptor.GetProperties()
{
// Get the name and type for each column name
var properties = from columnName in Columns
let columnIndex = Record.GetOrdinal(columnName)
let type = Record.GetFieldType(columnIndex)
select new DynamicPropertyDescriptor(columnName, type);
return new PropertyDescriptorCollection(properties.ToArray(), readOnly: true);
}
object ICustomTypeDescriptor.GetPropertyOwner(PropertyDescriptor pd)
{
return this;
}
private class DynamicPropertyDescriptor : PropertyDescriptor
{
private static readonly Attribute[] _empty = new Attribute[0];
private readonly Type _type;
public DynamicPropertyDescriptor(string name, Type type)
: base(name, _empty)
{
_type = type;
}
public override Type ComponentType
{
get { return typeof(EevaDynamicRecord); }
}
public override bool IsReadOnly
{
get { return true; }
}
public override Type PropertyType
{
get { return _type; }
}
public override bool CanResetValue(object component)
{
return false;
}
public override object GetValue(object component)
{
EevaDynamicRecord record = component as EevaDynamicRecord;
// REVIEW: Should we throw if the wrong object was passed in?
if (record != null)
{
return record[Name];
}
return null;
}
public override void ResetValue(object component)
{
throw new InvalidOperationException(
String.Format(CultureInfo.CurrentCulture,
"DataResources.RecordIsReadOnly", Name));
}
public override void SetValue(object component, object value)
{
throw new InvalidOperationException(
String.Format(CultureInfo.CurrentCulture,
"DataResources.RecordIsReadOnly", Name));
}
public override bool ShouldSerializeValue(object component)
{
return false;
}
}
}
}
I posted this also in http://forums.asp.net/p/2013821/5795169.aspx
Update
Problably creating a copy of every dynamicrecord solves the issue:
Dictionary<string, object> Clonedict;
public EevaDynamicRecord Clone()
{
var res = new EevaDynamicRecord(Columns, Record);
res.Clonedict = new Dictionary<string, object>();
for (int i = 0; i < Record.FieldCount; i++)
res.Clonedict[Record.GetName(i)] = Record[i]);
return res;
// this also does not work:
// return (EevaDynamicRecord)this.MemberwiseClone();
}
Is there better solution ?
I do not know that this will necessarily work, but to suggest it I needed to post an answer since it includes a code sample.
If I'm right and the problem is occurring because you're attempting to access data from a reader after the command has gone out of scope, this will work around that.
Replace your Test method with this:
public ActionResult Test()
{
var data = TestData().ToArray();
return View("ReportData", new ReportDataViewModel(data, ""));
}
If that doesn't help, try replacing the TestData method with this:
IEnumerable<dynamic> TestData()
{
List<dynamic> results = new List<dynamic>();
using (var connection = new NpgsqlConnection(ConnectionString()))
{
connection.Open();
DbCommand command = (DbCommand)connection.CreateCommand();
command.CommandText = "select 'A' union select 'B'";
using (command)
{
using (DbDataReader reader = command.ExecuteReader())
{
IEnumerable<string> columnNames = null;
while (reader.Read())
{
if (columnNames == null)
columnNames = GetColumnNames(reader);
results.Add(new EevaDynamicRecord(columnNames, reader));
}
//foreach (DbDataRecord record in reader)
//{
// if (columnNames == null)
// columnNames = GetColumnNames(record);
// yield return new EevaDynamicRecord(columnNames, record);
//}
}
}
}
return results;
}
I'm trying to expose a model to be available for OData services. The approach I'm currently taking is along the lines of:
1) Defining a class in the model to expose IQueryable collections such as:
public class MyEntities
{
public IQueryable<Customer> Customers
{
get
{
return DataManager.GetCustomers().AsQueryable<Customer>();
}
}
public IQueryable<User> Users
{
get
{
return DataManager.GetUsers().AsQueryable<User>();
}
}
}
2) Set up a WCF DataService with the queryable collection class such as:
public class MyDataService : DataService<MyEntities>
{
public static void InitializeService(DataServiceConfiguration config)
{
config.SetEntitySetAccessRule("Customers", EntitySetRights.All);
config.SetEntitySetAccessRule("Users", EntitySetRights.All);
config.DataServiceBehavior.MaxProtocolVersion = DataServiceProtocolVersion.V2;
}
}
I'm running into 3 issues and/or limitations with this approach:
1) I'm unable to add any derived class collections to the IQueryable lists.
2) I must apply the IgnoreProperties attribute to hide any members that are derived from a base type.
3) I'm unable to prevent unwanted entities from being accessed by the OData service and causing errors. For example, I only want BLL layer objects to be exposed, but it seems like the model is being reflected far beyond the members of the classes I added to the queryable list, and picking up all the DAL classes, causing errors being undefined and also having the same name as the BLL classes. There are no links to DAL classes from BLL class members. At the very least, I would like to have these classes ignored altogether.
Any pointers on how to address any of these issues would be greatly appreciated. Should I be doing a different approach on this? For example, should I implement IQueryable directly in my model collections?
Thanks.
The reflection provider which you're using is designed to walk all public types/properties. So the #3 and probably even #2 (which I don't fully understand what's the problem) are by design because of that.
#1 is also by design but for a different reason - the reflection provider can only expose one entity set for each type hierarchy. It doesn't support so called "MEST" (Multiple Entity Sets per Type), because it would not know which one to pick. It needs a 1 to 1 mapping between entity types and entity sets.
The reflection provider is meant for simple services which are "Easy" to setup. It's definitely not designed for customizations.
If you want greater control, then you need custom provider, which can be either implemented directly (if it's based on existing CLR classes it's not that hard), or through some library, like the one suggested in the comments above.
The reflection provider is not designed to handle rich data models with a fair amount of inheritance and other dependences. I ended up building a custom provider that could handle queries, updates, inheritance, and relationships based on Alex James' excellent blog post on Creating a Data Service Provider.
An example implementation with 3 CLR classes: ResidentialCustomer, Customer, and User is provided below. ResidentialCustomer extends Customer, Customer has a list of Users, and User has a reference back to Customer.
An interface for DataContext classes such as:
public interface IODataContext
{
IQueryable GetQueryable(ResourceSet set);
object CreateResource(ResourceType resourceType);
void AddResource(ResourceType resourceType, object resource);
void DeleteResource(object resource);
void SaveChanges();
}
A class to implement IDataServiceMetadataProvider such as:
public class ODataServiceMetadataProvider : IDataServiceMetadataProvider
{
private Dictionary<string, ResourceType> resourceTypes = new Dictionary<string, ResourceType>();
private Dictionary<string, ResourceSet> resourceSets = new Dictionary<string, ResourceSet>();
private List<ResourceAssociationSet> _associationSets = new List<ResourceAssociationSet>();
public string ContainerName
{
get { return "MyDataContext"; }
}
public string ContainerNamespace
{
get { return "MyNamespace"; }
}
public IEnumerable<ResourceSet> ResourceSets
{
get { return this.resourceSets.Values; }
}
public IEnumerable<ServiceOperation> ServiceOperations
{
get { yield break; }
}
public IEnumerable<ResourceType> Types
{
get { return this.resourceTypes.Values; }
}
public bool TryResolveResourceSet(string name, out ResourceSet resourceSet)
{
return resourceSets.TryGetValue(name, out resourceSet);
}
public bool TryResolveResourceType(string name, out ResourceType resourceType)
{
return resourceTypes.TryGetValue(name, out resourceType);
}
public bool TryResolveServiceOperation(string name, out ServiceOperation serviceOperation)
{
serviceOperation = null;
return false;
}
public void AddResourceType(ResourceType type)
{
type.SetReadOnly();
resourceTypes.Add(type.FullName, type);
}
public void AddResourceSet(ResourceSet set)
{
set.SetReadOnly();
resourceSets.Add(set.Name, set);
}
public bool HasDerivedTypes(ResourceType resourceType)
{
if (resourceType.InstanceType == typeof(ResidentialCustomer))
{
return true;
}
return false;
}
public IEnumerable<ResourceType> GetDerivedTypes(ResourceType resourceType)
{
List<ResourceType> derivedResourceTypes = new List<ResourceType>();
if (resourceType.InstanceType == typeof(ResidentialCustomer))
{
foreach (ResourceType resource in Types)
{
if (resource.InstanceType == typeof(Customer))
{
derivedResourceTypes.Add(resource);
}
}
}
return derivedResourceTypes;
}
public void AddAssociationSet(ResourceAssociationSet associationSet)
{
_associationSets.Add(associationSet);
}
public ResourceAssociationSet GetResourceAssociationSet(ResourceSet resourceSet, ResourceType resourceType, ResourceProperty resourceProperty)
{
return resourceProperty.CustomState as ResourceAssociationSet;
}
public ODataServiceMetadataProvider() { }
}
A class to implement IDataServiceQueryProvider such as:
public class ODataServiceQueryProvider<T> : IDataServiceQueryProvider where T : IODataContext
{
T _currentDataSource;
IDataServiceMetadataProvider _metadata;
public object CurrentDataSource
{
get
{
return _currentDataSource;
}
set
{
_currentDataSource = (T)value;
}
}
public bool IsNullPropagationRequired
{
get { return true; }
}
public object GetOpenPropertyValue(object target, string propertyName)
{
throw new NotImplementedException();
}
public IEnumerable<KeyValuePair<string, object>> GetOpenPropertyValues(object target)
{
throw new NotImplementedException();
}
public object GetPropertyValue(object target, ResourceProperty resourceProperty)
{
throw new NotImplementedException();
}
public IQueryable GetQueryRootForResourceSet(ResourceSet resourceSet)
{
return _currentDataSource.GetQueryable(resourceSet);
}
public ResourceType GetResourceType(object target)
{
Type type = target.GetType();
return _metadata.Types.Single(t => t.InstanceType == type);
}
public object InvokeServiceOperation(ServiceOperation serviceOperation, object[] parameters)
{
throw new NotImplementedException();
}
public ODataServiceQueryProvider(IDataServiceMetadataProvider metadata)
{
_metadata = metadata;
}
}
A class to implement IDataServiceUpdateProvider such as:
public class ODataServiceUpdateProvider<T> : IDataServiceUpdateProvider where T : IODataContext
{
private IDataServiceMetadataProvider _metadata;
private ODataServiceQueryProvider<T> _query;
private List<Action> _actions;
public T GetContext()
{
return ((T)_query.CurrentDataSource);
}
public void SetConcurrencyValues(object resourceCookie, bool? checkForEquality, IEnumerable<KeyValuePair<string, object>> concurrencyValues)
{
throw new NotImplementedException();
}
public void SetReference(object targetResource, string propertyName, object propertyValue)
{
_actions.Add(() => ReallySetReference(targetResource, propertyName, propertyValue));
}
public void ReallySetReference(object targetResource, string propertyName, object propertyValue)
{
targetResource.SetPropertyValue(propertyName, propertyValue);
}
public void AddReferenceToCollection(object targetResource, string propertyName, object resourceToBeAdded)
{
_actions.Add(() => ReallyAddReferenceToCollection(targetResource, propertyName, resourceToBeAdded));
}
public void ReallyAddReferenceToCollection(object targetResource, string propertyName, object resourceToBeAdded)
{
var collection = targetResource.GetPropertyValue(propertyName);
if (collection is IList)
{
(collection as IList).Add(resourceToBeAdded);
}
}
public void RemoveReferenceFromCollection(object targetResource, string propertyName, object resourceToBeRemoved)
{
_actions.Add(() => ReallyRemoveReferenceFromCollection(targetResource, propertyName, resourceToBeRemoved));
}
public void ReallyRemoveReferenceFromCollection(object targetResource, string propertyName, object resourceToBeRemoved)
{
var collection = targetResource.GetPropertyValue(propertyName);
if (collection is IList)
{
(collection as IList).Remove(resourceToBeRemoved);
}
}
public void ClearChanges()
{
_actions.Clear();
}
public void SaveChanges()
{
foreach (var a in _actions)
a();
GetContext().SaveChanges();
}
public object CreateResource(string containerName, string fullTypeName)
{
ResourceType type = null;
if (_metadata.TryResolveResourceType(fullTypeName, out type))
{
var context = GetContext();
var resource = context.CreateResource(type);
_actions.Add(() => context.AddResource(type, resource));
return resource;
}
throw new Exception(string.Format("Type {0} not found", fullTypeName));
}
public void DeleteResource(object targetResource)
{
_actions.Add(() => GetContext().DeleteResource(targetResource));
}
public object GetResource(IQueryable query, string fullTypeName)
{
var enumerator = query.GetEnumerator();
if (!enumerator.MoveNext())
throw new Exception("Resource not found");
var resource = enumerator.Current;
if (enumerator.MoveNext())
throw new Exception("Resource not uniquely identified");
if (fullTypeName != null)
{
ResourceType type = null;
if (!_metadata.TryResolveResourceType(fullTypeName, out type))
throw new Exception("ResourceType not found");
if (!type.InstanceType.IsAssignableFrom(resource.GetType()))
throw new Exception("Unexpected resource type");
}
return resource;
}
public object ResetResource(object resource)
{
_actions.Add(() => ReallyResetResource(resource));
return resource;
}
public void ReallyResetResource(object resource)
{
var clrType = resource.GetType();
ResourceType resourceType = _metadata.Types.Single(t => t.InstanceType == clrType);
var resetTemplate = GetContext().CreateResource(resourceType);
foreach (var prop in resourceType.Properties
.Where(p => (p.Kind & ResourcePropertyKind.Key) != ResourcePropertyKind.Key))
{
var clrProp = clrType.GetProperties().Single(p => p.Name == prop.Name);
var defaultPropValue = clrProp.GetGetMethod().Invoke(resetTemplate, new object[] { });
clrProp.GetSetMethod().Invoke(resource, new object[] { defaultPropValue });
}
}
public object ResolveResource(object resource)
{
return resource;
}
public object GetValue(object targetResource, string propertyName)
{
var value = targetResource.GetType().GetProperties().Single(p => p.Name == propertyName).GetGetMethod().Invoke(targetResource, new object[] { });
return value;
}
public void SetValue(object targetResource, string propertyName, object propertyValue)
{
targetResource.GetType().GetProperties().Single(p => p.Name == propertyName).GetSetMethod().Invoke(targetResource, new[] { propertyValue });
}
public ODataServiceUpdateProvider(IDataServiceMetadataProvider metadata, ODataServiceQueryProvider<T> query)
{
_metadata = metadata;
_query = query;
_actions = new List<Action>();
}
}
A class to implement IServiceProvider such as:
public class ODataService<T> : DataService<T>, IServiceProvider where T : IODataContext
{
private ODataServiceMetadataProvider _metadata;
private ODataServiceQueryProvider<T> _query;
private ODataServiceUpdateProvider<T> _updater;
public object GetService(Type serviceType)
{
if (serviceType == typeof(IDataServiceMetadataProvider))
{
return _metadata;
}
else if (serviceType == typeof(IDataServiceQueryProvider))
{
return _query;
}
else if (serviceType == typeof(IDataServiceUpdateProvider))
{
return _updater;
}
else
{
return null;
}
}
public ODataServiceMetadataProvider GetMetadataProvider(Type dataSourceType)
{
ODataServiceMetadataProvider metadata = new ODataServiceMetadataProvider();
ResourceType customer = new ResourceType(
typeof(Customer),
ResourceTypeKind.EntityType,
null,
"MyNamespace",
"Customer",
false
);
ResourceProperty customerCustomerID = new ResourceProperty(
"CustomerID",
ResourcePropertyKind.Key |
ResourcePropertyKind.Primitive,
ResourceType.GetPrimitiveResourceType(typeof(Guid))
);
customer.AddProperty(customerCustomerID);
ResourceProperty customerCustomerName = new ResourceProperty(
"CustomerName",
ResourcePropertyKind.Primitive,
ResourceType.GetPrimitiveResourceType(typeof(string))
);
customer.AddProperty(customerCustomerName);
ResourceType residentialCustomer = new ResourceType(
typeof(ResidentialCustomer),
ResourceTypeKind.EntityType,
customer,
"MyNamespace",
"ResidentialCustomer",
false
);
ResourceType user = new ResourceType(
typeof(User),
ResourceTypeKind.EntityType,
null,
"MyNamespace",
"User",
false
);
ResourceProperty userUserID = new ResourceProperty(
"UserID",
ResourcePropertyKind.Key |
ResourcePropertyKind.Primitive,
ResourceType.GetPrimitiveResourceType(typeof(Guid))
);
user.AddProperty(userUserID);
ResourceProperty userCustomerID = new ResourceProperty(
"CustomerID",
ResourcePropertyKind.Primitive,
ResourceType.GetPrimitiveResourceType(typeof(Guid))
);
user.AddProperty(userCustomerID);
ResourceProperty userEmailAddress = new ResourceProperty(
"EmailAddress",
ResourcePropertyKind.Primitive,
ResourceType.GetPrimitiveResourceType(typeof(string))
);
user.AddProperty(userEmailAddress);
var customerSet = new ResourceSet("Customers", customer);
var residentialCustomerSet = new ResourceSet("ResidentialCustomers", residentialCustomer);
var userSet = new ResourceSet("Users", user);
var userCustomer = new ResourceProperty(
"Customer",
ResourcePropertyKind.ResourceReference,
customer
);
user.AddProperty(userCustomer);
var customerUserList = new ResourceProperty(
"UserList",
ResourcePropertyKind.ResourceSetReference,
user
);
customer.AddProperty(customerUserList);
metadata.AddResourceType(customer);
metadata.AddResourceSet(customerSet);
metadata.AddResourceType(residentialCustomer);
metadata.AddResourceSet(residentialCustomerSet);
metadata.AddResourceType(user);
metadata.AddResourceSet(userSet);
ResourceAssociationSet customerUserListSet = new ResourceAssociationSet(
"CustomerUserList",
new ResourceAssociationSetEnd(
customerSet,
customer,
customerUserList
),
new ResourceAssociationSetEnd(
userSet,
user,
userCustomer
)
);
customerUserList.CustomState = customerUserListSet;
userCustomer.CustomState = customerUserListSet;
metadata.AddAssociationSet(customerUserListSet);
return metadata;
}
public ODataServiceQueryProvider<T> GetQueryProvider(ODataServiceMetadataProvider metadata)
{
return new ODataServiceQueryProvider<T>(metadata);
}
public ODataServiceUpdateProvider<T> GetUpdateProvider(ODataServiceMetadataProvider metadata, ODataServiceQueryProvider<T> query)
{
return new ODataServiceUpdateProvider<T>(metadata, query);
}
public ODataService()
{
_metadata = GetMetadataProvider(typeof(T));
_query = GetQueryProvider(_metadata);
_updater = GetUpdateProvider(_metadata, _query);
}
}
The DataContext class holds the CLR collections and wires up the service operations such as:
public partial class MyDataContext: IODataContext
{
private List<Customer> _customers = null;
public List<Customer> Customers
{
get
{
if (_customers == null)
{
_customers = DataManager.GetCustomers);
}
return _customers;
}
}
private List<ResidentialCustomer> _residentialCustomers = null;
public List<ResidentialCustomer> ResidentialCustomers
{
get
{
if (_residentialCustomers == null)
{
_residentialCustomers = DataManager.GetResidentialCustomers();
}
return _residentialCustomers;
}
}
private List<User> _users = null;
public List<User> Users
{
get
{
if (_users == null)
{
_users = DataManager.GetUsers();
}
return _users;
}
}
public IQueryable GetQueryable(ResourceSet set)
{
if (set.Name == "Customers") return Customers.AsQueryable();
if (set.Name == "ResidentialCustomers") return ResidentialCustomers.AsQueryable();
if (set.Name == "Users") return Users.AsQueryable();
throw new NotSupportedException(string.Format("{0} not found", set.Name));
}
public object CreateResource(ResourceType resourceType)
{
if (resourceType.InstanceType == typeof(Customer))
{
return new Customer();
}
if (resourceType.InstanceType == typeof(ResidentialCustomer))
{
return new ResidentialCustomer();
}
if (resourceType.InstanceType == typeof(User))
{
return new User();
}
throw new NotSupportedException(string.Format("{0} not found for creating.", resourceType.FullName));
}
public void AddResource(ResourceType resourceType, object resource)
{
if (resourceType.InstanceType == typeof(Customer))
{
Customer i = resource as Customer;
if (i != null)
{
Customers.Add(i);
return;
}
}
if (resourceType.InstanceType == typeof(ResidentialCustomer))
{
ResidentialCustomeri = resource as ResidentialCustomer;
if (i != null)
{
ResidentialCustomers.Add(i);
return;
}
}
if (resourceType.InstanceType == typeof(User))
{
Useri = resource as User;
if (i != null)
{
Users.Add(i);
return;
}
}
throw new NotSupportedException(string.Format("{0} not found for adding.", resourceType.FullName));
}
public void DeleteResource(object resource)
{
if (resource.GetType() == typeof(Customer))
{
Customers.Remove(resource as Customer);
return;
}
if (resource.GetType() == typeof(ResidentialCustomer))
{
ResidentialCustomers.Remove(resource as ResidentialCustomer);
return;
}
if (resource.GetType() == typeof(User))
{
Users.Remove(resource as User);
return;
}
throw new NotSupportedException(string.Format("{0} not found for deletion.", resource.GetType().FullName));
}
public void SaveChanges()
{
foreach (var item in Customers.Where(i => i.IsModified == true))
item.Save();
foreach (var item in ResidentialCustomers.Where(i => i.IsModified == true))
item.Save();
foreach (var item in Users.Where(i => i.IsModified == true))
item.Save();
}
}
Then, create your data service using the custom data service class and your data context, such as:
public class MyDataService : ODataService<MyDataContext>
{
public static void InitializeService(DataServiceConfiguration config)
{
config.SetEntitySetAccessRule("Customers", EntitySetRights.All);
config.SetEntitySetAccessRule("ResidentialCustomers", EntitySetRights.All);
config.SetEntitySetAccessRule("Users", EntitySetRights.All);
config.DataServiceBehavior.MaxProtocolVersion = DataServiceProtocolVersion.V2;
config.DataServiceBehavior.AcceptProjectionRequests = true;
}
}
Lots of wiring up, but pretty straightforward once you've got the hang of it.