How share data in WCF webservice - c#

In order to call webservices dynamicly, I use WCF Dynamic Proxy from Microsoft
If I understood properly how it works, the code load the wsdl and compile on system class in order to consume distant webservice. I put this code in a "generic webservice". Its goal is to call any webservice with a request in parameter, and respond the answer of the webservice called.
But a problem appears : each request to this "generic webservice" pulls a new compilation of the proxy, and use time and ressources of the server.
My objective is to save instance of each proxies during a laps of time, and renew the instance when this laps is reached.
After few hours of googling, I found two ways :
Use my WCF webservice "by session", but I don't find any tutorial which explains how create easily the session layer
Use a singleton in order to save my datas and mutualize them with all instances of webservice
I exclude the first solution because I don't know how to do this. So I decided to use the second way.
There is my implementation :
FactoryTest is the singleton, contening the hashtable with instances
ProxyTest is the class which contains information about each instances of distant webservices
There is the code of FactoryTest :
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using WcfSamples.DynamicProxy;
using System.Threading;
using System.Collections;
namespace WS_Generic
{
public sealed class FactoryTest
{
private static object syncRoot = new Object();
private static Hashtable hashFactory = new Hashtable();
public static DynamicProxy getProxy(String sServiceWsdl, String sContract)
{
if (hashFactory[sServiceWsdl] == null || ((ProxyTest)hashFactory[sServiceWsdl]).getTimeFromCreation().TotalSeconds > 60 * 60 * 6)
{
lock (syncRoot)
{
if (hashFactory[sServiceWsdl] == null || ((ProxyTest)hashFactory[sServiceWsdl]).getTimeFromCreation().TotalSeconds > 60 * 60 * 6)
{
hashFactory.Add(sServiceWsdl, new ProxyTest(sServiceWsdl, sContract));
}
}
}
return ((ProxyTest)hashFactory[sServiceWsdl]).getProxy();
}
public static bool isProxyExists(String sServiceWsdl, String sContract)
{
lock (syncRoot)
{
return hashFactory[sServiceWsdl] == null ? false : true;
}
}
}
}
There is the code of ProxyTest :
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using WcfSamples.DynamicProxy;
namespace WS_Generic
{
public class ProxyTest
{
private DateTime instanceCreation;
private String sServiceWsdl;
private String sContract;
private DynamicProxyFactory factory;
private volatile DynamicProxy proxy;
public ProxyTest(String sServiceWsdl, String sContract)
{
instanceCreation = DateTime.Now;
this.sServiceWsdl = sServiceWsdl;
this.sContract = sContract;
this.factory = new DynamicProxyFactory(this.sServiceWsdl);
this.proxy = factory.CreateProxy(this.sContract);
}
public DynamicProxy getProxy()
{
return proxy;
}
public TimeSpan getTimeFromCreation()
{
return DateTime.Now.Subtract(instanceCreation);
}
}
}
The problem is the webservice seems to reset the static status of FactoryTest after each call. So each time I called the webservice, my hashtable is empty and the factory create a new instance.
If anybody had already the problem of share datas between differents threads in WCF webservice (and found the solution), thanks in advance to give me some tips :)
PS : Sorry for my english, that's not my native language

If you store data in static variable WCF itself will not affect their purging. The problem must be somewhere else (application restart, app domain recreation, etc.).
Btw. this solution has only very limited usage because long living shared proxies should not be used and in many cases it can result in unexpected behavior. It can perhaps work only for services using basicHttpBinding.

Related

ExcelDNA RTD with SignalR

I'm trying to hook up ExcelDNA RTD with a ASP.NET SignalR server.
Whenever there is a change on the server a message get pushed to the connected clients, and my ExcelDna add-in is getting the new messages but the registered function is not updated.
My RTD server:
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.SignalR.Client;
using ExcelDna.Integration;
using ExcelDna.Integration.Rtd;
namespace DMT.Excel.AddIn
{
[ComVisible(true)]
public class SignalRServer : ExcelRtdServer
{
private HubConnection _connection;
private List<Topic> _topics = new List<Topic>();
public TradesRtdServer()
{
_connection = new HubConnectionBuilder()
.WithUrl("http://localhost:5000/api/test/hub")
.WithAutomaticReconnect()
.Build();
_connection.On<object>("Test", m =>
{
foreach (Topic topic in _topics)
{
topic.UpdateValue(m);
}
});
Task.Run(() => _connection.StartAsync());
}
protected override bool ServerStart()
{
DmtAddIn.Logger.Information("ServerStart");
return true;
}
protected override void ServerTerminate()
{
DmtAddIn.Logger.Information("ServerTerminate");
}
protected override object ConnectData(Topic topic, IList<string> topicInfo, ref bool newValues)
{
DmtAddIn.Logger.Information("ConnectData: {0} - {{{1}}}", topic.TopicId, string.Join(", ", topicInfo));
_topics.Add(topic);
return ExcelErrorUtil.ToComError(ExcelError.ExcelErrorNA);
}
protected override void DisconnectData(Topic topic)
{
_topics.Remove(topic);
DmtAddIn.Logger.Information("DisconnectData: {0}", topic.TopicId);
}
}
}
My function
[ExcelFunction(Name = "SignalR.Test.RTD")]
public static object GetSignalRMessages()
{
return XlCall.RTD("Excel.AddIn.Trading.SignalRServer", null, "Test");
}
When I debug I can see topic.UpdateValue(m); is being hit whenever a message is pushed from the server but not GetSignalRMessages
Am I missing anything to propagate the topic change to the function?
Thank you!
Joseph
I managed to solve this by sending a string from the SignalR server, then deserialize it on the client side
The ExcelRtdServer checks whether the value passed into UpdateValue is different to the previous value. You might be passing either the same value every time, or some value that is interpreted to an Excel data type as the same (e.g. some object type that is converted to the same string every time).
You might be better off building this through the IObservable or Rx abstractions, that are a bit higher-level than the ExcelRtdServer. See the samples here https://github.com/Excel-DNA/Samples/tree/master/RtdClocks
Maybe something like this project which combines Rx and SignalR: https://github.com/jwooley/SignalrRxSamples

How Can I Simplify Classes That Do Similar Things

I am working on a project where I call an external API. I want to cache the API responses for a certain amount of time and have created a 5 classes that all do practically the same thing: get data from cache or if the cache doesn't have data, get data from the API.
I'm looking for suggestions on how to simply this code.
Class:
using DataCreator.Foo.Api;
using DataCreator.Foo.Models;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.DependencyInjection;
using System;
using System.Collections.Generic;
namespace DataCreator.Foo.DataProvider
{
public class PerformanceStatusDataProvider
{
private readonly IMemoryCache memoryCache;
public PerformanceStatusDataProvider(IServiceProvider serviceProvider)
{
memoryCache = serviceProvider.GetService<IMemoryCache>();
}
public List<PerformanceStatus> GetPerformanceStatuses()
{
return memoryCache.GetOrCreate("performance_status", cacheEntry =>
{
cacheEntry.AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(1);
return FooApi.Instance.GetPerformanceStatuses();
});
}
}
}
If all your DataProviders follow a similar syntax, it should be quite easy with generics:
public List<T> GetData<T>(string cacheKey, Func<List<T>> retrieveDataFromApi)
{
return memoryCache.GetOrCreate(cacheKey, cacheEntry =>
{
cacheEntry.AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(1);
return retrieveDataFromApi();
});
}
You'd call this using something like:
GenericDataProvider.GetData("performance_status", FooApi.Instance.GetPerformanceStatuses);
Small details: you're calling an API and you're not using async/await (you most definitively should) and FooApi.Instance is a code smell, you're 50% using Dependency Injection and 50% hard-coding dependencies.

Asp.net Core DI: Using SemaphoreSlim for write AND read operations with Singleton

I am re-tooling an ASP.NET CORE 2.2 app to avoid using the service locator pattern in conjunction with static classes. Double bad!
The re-tooling is involving the creation and injection of Singleton object as a repository for some global data. The idea here to avoid hits to my SQL server for some basic/global data that gets used over and over again in requests. However, this data needs to be updated on an hourly basis (not just at app startup). So, to manage the situation I am using SemaphoreSlim to handle one-at-a-time access to the data objects.
Here is a paired down sketch of what what I'm doing:
namespace MyApp.Global
{
public interface IMyGlobalDataService
{
Task<List<ImportantDataItem>> GetFilteredDataOfMyList(string prop1);
Task LoadMyImportantDataListAsync();
}
public class MyGlobalDataService: IMyGlobalDataService
{
private MyDbContext _myDbContext;
private readonly SemaphoreSlim myImportantDataLock = new SemaphoreSlim(1, 1);
private List<ImportantDataItem> myImportantDataList { get; set; }
public async Task<List<ImportantDataItem>> GetFilteredDataOfMyList(string prop1)
{
List<ImportantDataItem> list;
myImportantDataLock.WaitAsync();
try
{
list = myImportantDataList.Where(itm => itm.Prop1 == prop1).ToList();
}
finally
{
myImportantDataLock.Release();
}
return list;
}
public async Task LoadMyImportantDataListAsync()
{
// this method gets called when the Service is created and once every hour thereafter
myImportantDataLock.WaitAsync();
try
{
this.MyImportantDataList = await _myDbContext.ImportantDataItems.ToListAsync();
}
finally
{
myImportantDataLock.Release();
}
return;
}
public MyGlobalDataService(MyDbContext myDbContext) {
_myDbContext = myDbContext;
};
}
}
So in effect I am using the SemaphoreSlim to limit to one-thread-at-a-time access, for both READ and UPDATING to myImportantDataList. This is really uncertain territory for me. Does this seem like an appropriate approach to handle my injection of a global data Singleton throughout my app? Or should I expect insane thread locking/blocking?
The problem with using SemaphoreSlim is scalability.
As this is in a web application, it's fair to assume that you want to have the potential for more than one reader to access the data simultaneously. However, you are (understandably) limiting the number of requests for the semaphore that can be generated concurrently to 1 (to prevent concurrent read and write requests). This means you will serialize all reads too.
You need to use something like ReaderWriterLockSlim to allow multiple threads for reading, but ensure exclusive access for writing.
Creyke's answer hit the nail on the head for me: using ReaderWriterLockSlim. So I've marked it as the accepted answer. But I am posting my revised solution in case it might be helpful to anyone. Important to note that I'm using the following package to provide async functionality to ReaderWriterLockSlim: https://www.nuget.org/packages/Nito.AsyncEx/
using Nito.AsyncEx;
using System;
using System.Collections.Generic;
using System.Text;
namespace MyApp.Global
{
public interface IMyGlobalDataService
{
Task<List<ImportantDataItem>> GetFilteredDataOfMyList(string prop1);
Task LoadMyImportantDataListAsync();
}
public class MyGlobalDataService : IMyGlobalDataService
{
private MyDbContext _myDbContext;
private readonly AsyncReaderWriterLock myImportantDataLock = new AsyncReaderWriterLock();
private List<ImportantDataItem> myImportantDataList { get; set; }
public async Task<List<ImportantDataItem>> GetFilteredDataOfMyList(string prop1)
{
List<ImportantDataItem> list;
using (await myImportantDataLock.ReaderLockAsync())
{
list = myImportantDataList.Where(itm => itm.Prop1 == prop1).ToList();
}
return list;
}
public async Task LoadMyImportantDataListAsync()
{
// this method gets called when the Service is created and once every hour thereafter
using (await myImportantDataLock.WriterLockAsync())
{
this.MyImportantDataList = await _myDbContext.ImportantDataItems.ToListAsync();
}
return;
}
public MyGlobalDataService(MyDbContext myDbContext)
{
_myDbContext = myDbContext;
};
}
}

Maintain state between WCF webservice calls from android

I have a project in VB.NET which is using asp.net membership to manage user authentication. Now I want to build android app for this project so I decided to learn WCF and I have got average hold on WCF webservices. Now the issue I am facing is that when the user login into the android app following things happen:
Request goes to the webapplication and user credentials are authenticated.
After that when the user tries submits any data or try to view data , request again goes to the web application but now the web application should authenticate the user based on the credentials he has provided in the first request for login from the membership authentication.
Now the issue I am facing how to authenticate user in asp.net membership for each WCF Request in Per-Session Service call mode from java(Android).
There's several ways to do what I think you're asking for, I've thought of (and written) a few different potential solutions, however, the one I'm sharing here is something that I think will "slot-in" to existing solutions using ASP.NET Membership/Roles Provider. Hopefully I've given you enough information to do what you need to do, but you can always comment and ask more questions if anything's still unclear.
In your problem you describe using an ASP.NET Web Application containing a WCF Service for existing clients, but you're looking to expand to using Android (java) requests? Given that the ASP.NET Membership provider uses alot of "behind the scenes" SOAP interchanges (for authentication, authorization and encryption) that seem to be built into the service reference frameworks it'd be a fairly big task to write a java implementation...
So, I've written you an example of something that will integrate to the same "backend" provider, but will also allow you to send SOAP requests from any client without needing the service reference (I tested it using SoapUI for example)... I've written my solution in C# (as it's what the WCF samples were written in), however, you can quite easily use a code-converter to switch it to VB.NET. I also haven't provided you with the method to encrypt and decrypt passwords, you'll have to research that bit yourself.
You'll need to implement a new .svc file into your existing solution and create new web.config entries accordingly (I assume you know how to create a basicHttpBinding and service endpoint already).
You'll also need to duplicate your method calls (or instead, create a new class with the method content and reference it from wherever you're implementing the ServiceContract methods) and remove the "[PrincipalPermission(SecurityAction" attributes, and add the example methods below into the new service.
e.g. (using methods from Microsoft's MembershipAndRoleProvider WCF Sample) -
// Allows all Users to call the Add method
[PrincipalPermission(SecurityAction.Demand, Role = "Users")]
public double Add(double n1, double n2)
{
double result = n1 + n2;
return result;
}
Would become:
// Allows all Users to call the Add method
public double Add(double n1, double n2, string username, string token)
{
string isAuthorized = IsAuthorized(username, "Users", token)
if (isAuthorized.Contains("Success")
double result = n1 + n2;
return result;
else
throw new Exception("Authorization Exception: " + isAuthorized);
}
Here's my implementation(s), integrated into the Microsoft WCF Sample MembershipAndRoleProvider (download from here):
IsolatedAuthService.svc
<%#ServiceHost Language="C#" Debug="true" Service="Microsoft.ServiceModel.Samples.IsolatedAuthService" CodeBehind="IsolatedAuthService.cs" %>
IIsolatedAuthService.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.Text;
namespace Microsoft.ServiceModel.Samples
{
// Define a service contract.
[ServiceContract(Namespace = "http://Microsoft.ServiceModel.Samples")]
public interface IIsolatedAuthService
{
[OperationContract]
string IsAuthorized(string username, string roleName, string token);
[OperationContract]
string AuthenticateUser(string username, string encryptedPassword);
}
}
IsolatedAuthService.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.Text;
using System.Web;
using System.Web.Hosting;
using System.Web.Security;
using System.Web.Configuration;
using System.Configuration;
using System.IO;
using System.Security.Permissions;
using System.Security.Principal;
using System.ServiceModel.Activation;
using System.Threading;
namespace Microsoft.ServiceModel.Samples
{
public class IsolatedAuthService : IIsolatedAuthService
{
public string IsAuthorized(string username, string roleName, string token)
{
MembershipUser user = Membership.GetAllUsers()[username];
Configuration config = ConfigurationManager.OpenExeConfiguration(HostingEnvironment.MapPath("~") + "\\web.config");
SessionStateSection sessionStateConfig = (SessionStateSection)config.SectionGroups.Get("system.web").Sections.Get("sessionState");
InMemoryInstances instance = InMemoryInstances.Instance;
// Check for session state timeout (could use a constant here instead if you don't want to rely on the config).
if (user.LastLoginDate.AddMinutes(sessionStateConfig.Timeout.TotalMinutes) < DateTime.Now)
{
// Remove token from the singleton in this instance, effectively a logout.
instance.removeTokenUserPair(username);
return "User Unauthorized - login has expired!";
}
if (!instance.checkTokenUserPair(username, token))
return "User Unauthorized - not a valid token!";
// Check for role membership.
if (!Roles.GetUsersInRole(roleName).Contains(user.UserName))
return "User Unauthorized - Does not belong in that role!";
return "Success - User is Authorized!";
}
public string AuthenticateUser(string username, string encryptedPassword)
{
if (Membership.ValidateUser(username, Decrypt(encryptedPassword)))
{
// Not sure if this is actually needed, but reading some documentation I think it's a safe bet to do here anyway.
Membership.GetAllUsers()[username].LastLoginDate = DateTime.Now;
// Send back a token!
Guid token = Guid.NewGuid();
// Store a token for this username.
InMemoryInstances instance = InMemoryInstances.Instance;
instance.removeTokenUserPair(username); //Because we don't implement a "Logout" method.
instance.addTokenUserPair(username, token.ToString());
return token.ToString();
}
return "Error - User was not able to be validated!";
}
}
}
InMemoryInstances.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Microsoft.ServiceModel.Samples
{
public class InMemoryInstances
{
private static volatile InMemoryInstances instance;
private static object syncRoot = new Object();
private Dictionary<string, string> usersAndTokens = null;
private InMemoryInstances()
{
usersAndTokens = new Dictionary<string, string>();
}
public static InMemoryInstances Instance
{
get
{
if (instance == null)
{
lock (syncRoot)
{
if (instance == null)
instance = new InMemoryInstances();
}
}
return instance;
}
}
public void addTokenUserPair(string username, string token)
{
usersAndTokens.Add(username, token);
}
public bool checkTokenUserPair(string username, string token)
{
if (usersAndTokens.ContainsKey(username)) {
string value = usersAndTokens[username];
if (value.Equals(token))
return true;
}
return false;
}
public void removeTokenUserPair(string username)
{
usersAndTokens.Remove(username);
}
}
}
Bare in mind, this solution will not work if you're load-balancing your WCF service across multiple servers (due to the in-memory instance class), you could change the solution to use a database table instead of the in-memory instances if this is a requirement for you.

RemotingServices.IsObjectOutOfAppDomain when it will return false?

As per Remote Object definition- Any object outside the application domain of the caller should be considered remote.
RemotingServices.IsObjectOutOfAppDomain- returns false if remote object resides in the same app domain.
In the MSDN article Microsoft .NET Remoting: A Technical Overview I
found the following statement (in the paragraph "Proxy Objects") about
method calls on remote objects:
...the [method] call is examined to determine if it is a valid method
of the remote object and if an instance of the remote object resides in
the same application domain as the proxy. If this is true, a simple
method call is routed to the actual object.
So I am surprised when the remote object and proxy will reside in the same app domain.
sample example:
using System;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Tcp;
namespace RemotingSamples
{
public class HelloServer : MarshalByRefObject
{
public HelloServer()
{
Console.WriteLine("HelloServer activated");
}
public String HelloMethod(String name)
{
return "Hi there " + name;
}
}
public class Server
{
public static int Main(string [] args)
{
// server code
ChannelServices.RegisterChannel(new TcpChannel(8085));
RemotingConfiguration.RegisterWellKnownServiceType(
typeof(HelloServer), "SayHelloSingleton",
WellKnownObjectMode.Singleton);
// client code
HelloServer obj = HelloServer)Activator.GetObject(
typeof(HelloServer), "tcp://localhost:8085/SayHelloSingleton");
System.Console.WriteLine(
"IsTransparentProxy={0}, IsOutOfAppDomain={1}",
RemotingServices.IsTransparentProxy(obj),
RemotingServices.IsObjectOutOfAppDomain(obj));
Console.WriteLine(obj.HelloMethod("server"));
return 0;
}
}
}
Well, one obvious case when it will return false is when the object isn't a proxy, but is a regular .NET object in the local domain (no remoting involved).
I don't understand the MSDN note fully, either ;-p

Categories