How to manage client connections in a Blazor Server + ASP.NET Core API + SignalR project - razor

I am working on a Blazor Server app over a SignalR connection with an ASP.NET Core API to send real-time updates from the server to clients. However, I am having a problem with managing user connections.
The uniqueness of the problem is that each tab opened in a browser by a user represents an individual connection on the SignalR server. This becomes a problem when a user has multiple tabs open with the application and each of them is generating a unique connection. This is because each tab is considered a unique session and therefore the SignalR server creates a new connection for each tab. For example:
If a user "User1" opens 3 tabs in their browser, 3 individual connections will be created for User1 on the server.
If another user "User2" opens 2 tabs in their browser, 2 more
connections will be created for User2 on the server.
And if I'm not logged in, and I open 3 tabs, it will create 3 more connections on
the server.
The desired environment, regardless of the number of tabs open, instead of duplicating connections:
User1 = 1 connection.
User2 = 1 connection.
Not logged = 1 connection.
My question is how can I effectively manage user connections so that there is only one connection per user/client/session instead of as many as opened tabs. Has anyone had a similar problem and knows how to solve it? I'm sorry if there is an usual easy know fix for this, but I'm looking around and I can't find something that fits exactly my behaviour; and I need some orientation on here instead of copy-paste some code, since conections managment it's a core feature and I'm not much familiar with these.
To clarifly some solutions I've tried are:
Sol. A) In the client: AddSingleton instead of AddScoped
Sol. B) In the client: Set the ConnectionId after hubConn.StartAsync()
Sol. B) In the server: Clients.Client(Context.ConnectionId).SendAsync() instead of Clients.All.SendAsync()
And to mention I didn't used services.AddHttpClient() w/
IClientFactory, but I dont know if it's needed at all or if it's involved in the problem.
Thank you for your time and help!
I provide code used in the connections below:
ASP.NET Core API - SERVER:
Program.cs
using ChartServer.DataProvider;
using ChartServer.RHub;
using SharedModels;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
// Add CORS Policy
builder.Services.AddCors(option => {
option.AddPolicy("cors", policy => {
policy.AllowAnyOrigin().AllowAnyHeader().AllowAnyHeader();
});
});
builder.Services.AddSignalR();
// Register the Watcher
builder.Services.AddScoped<TimeWatcher>();
builder.Services.AddControllers();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseHttpsRedirection();
app.UseCors("cors");
app.UseAuthorization();
app.MapControllers();
// Add the SignalR Hub
app.MapHub<MarketHub>("/marketdata");
app.Run();
MarketController.cs
namespace ChartServer.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class MarketController : ControllerBase
{
private IHubContext<MarketHub> marketHub;
private TimeWatcher watcher;
public MarketController(IHubContext<MarketHub> mktHub, TimeWatcher watch)
{
marketHub = mktHub;
watcher = watch;
}
[HttpGet]
public IActionResult Get()
{
if(!watcher.IsWatcherStarted)
{
watcher.Watcher(()=>marketHub.Clients.All.SendAsync("SendMarketStatusData",MarketDataProvider.GetMarketData()));
}
return Ok(new { Message = "Request Completed" });
}
}
}
MarketHub.cs
namespace ChartServer.RHub
{
public class MarketHub : Hub
{
public async Task AcceptData(List<Market> data) =>
await Clients.All.SendAsync("CommunicateMarketData", data);
}
}
TimeWatcher.cs
namespace ChartServer.DataProvider
{
/// <summary>
/// This call will be used to send the data after each second to the client
/// </summary>
public class TimeWatcher
{
private Action? Executor;
private Timer? timer;
// we need to auto-reset the event before the execution
private AutoResetEvent? autoResetEvent;
public DateTime WatcherStarted { get; set; }
public bool IsWatcherStarted { get; set; }
/// <summary>
/// Method for the Timer Watcher
/// This will be invoked when the Controller receives the request
/// </summary>
public void Watcher(Action execute)
{
int callBackDelayBeforeInvokeCallback = 1000;
int timeIntervalBetweenInvokeCallback = 2000;
Executor = execute;
autoResetEvent = new AutoResetEvent(false);
timer = new Timer((object? obj) => {
Executor();
}, autoResetEvent, callBackDelayBeforeInvokeCallback, timeIntervalBetweenInvokeCallback);
WatcherStarted = DateTime.Now;
IsWatcherStarted = true;
}
}
}
Blazor Server app - CLIENT:
Program.cs
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Web;
using SignalsServer.Data;
using SignalsServer.HttpCaller;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddRazorPages();
builder.Services.AddServerSideBlazor();
builder.Services.AddSingleton<WeatherForecastService>();
builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri("https://localhost:7084/") });
builder.Services.AddScoped<MarketDataCaller>();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.MapBlazorHub();
app.MapFallbackToPage("/_Host");
app.Run();
MarketDataCaller.cs
namespace SignalsServer.HttpCaller
{
public class MarketDataCaller
{
private HttpClient httpClient;
public MarketDataCaller(HttpClient http)
{
httpClient = http;
}
public async Task GetMarketDataAsync()
{
try
{
var response = await httpClient.GetAsync("marketdata");
if (!response.IsSuccessStatusCode)
throw new Exception("Something is wrong with the connection make sure that the server is running.");
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
throw ex;
}
}
public async Task GetMarketEndpoint()
{
try
{
var response = await httpClient.GetAsync("https://localhost:7193/api/Market");
if (!response.IsSuccessStatusCode)
throw new Exception("Something is wrong with the connection so get call is not executing.");
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
throw ex;
}
}
}
}
ChartComponent.razor
#page "/chartui"
#using Microsoft.AspNetCore.SignalR.Client;
#using SharedModels
#using System.Text.Json
#inject IJSRuntime js
#inject SignalsServer.HttpCaller.MarketDataCaller service;
<h3>Chart Component</h3>
<div>
<div class="container">
<table class="table table-bordered table-striped">
<tbody>
<tr>
<td>
<button class="btn btn-success"
#onclick="#generateLineChartTask">Line Chart</button>
</td>
<td>
<button class="btn btn-danger"
#onclick="#generateBarChartTask">Bar Chart</button>
</td>
</tr>
</tbody>
</table>
<div id="market"></div>
<table class="table table-bordered table-striped">
<thead>
<tr>
<th>Company Name</th>
<th>Volume</th>
</tr>
</thead>
<tbody>
#foreach (var item in MarketData)
{
<tr>
<td>#item.CompanyName</td>
<td>#item.Volume</td>
</tr>
}
</tbody>
</table>
<hr/>
<div class="container">
#ConnectionStatusMessage
</div>
</div>
</div>
#code {
private HubConnection? hubConn;
private string? ConnectionStatusMessage;
public List<Market> MarketData = new List<Market>();
public List<Market> MarketReceivedData = new List<Market>();
private List<string> xSource;
private List<int> ySource;
private List<object> source;
protected override async Task OnInitializedAsync()
{
xSource = new List<string>();
ySource = new List<int>();
source = new List<object>();
await service.GetMarketEndpoint();
hubConn = new HubConnectionBuilder().WithUrl("https://localhost:7193/marketdata").Build();
await hubConn.StartAsync();
if(hubConn.State == HubConnectionState.Connected )
ConnectionStatusMessage = "Connection is established Successfully...";
else
ConnectionStatusMessage = "Connection is not established...";
}
private int contador = 0;
private void MarketDataListener(string chartType)
{
hubConn.On<List<Market>>("SendMarketStatusData", async (data) =>
{
MarketData = new List<Market>();
foreach (var item in data)
{
Console.WriteLine($"Company Name: {item.CompanyName}, Volumn: {item.Volume}");
xSource.Add(item.CompanyName);
ySource.Add(item.Volume);
}
source.Add(ySource);
source.Add(xSource);
MarketData = data;
contador++;
Console.WriteLine($"CONTADOR: {contador}");
InvokeAsync(StateHasChanged);
await js.InvokeAsync<object>(chartType, source.ToArray());
xSource.Clear();
ySource.Clear();
});
}
private void ReceivedMarketDataListener()
{
hubConn.On<List<Market>>("CommunicateMarketData", (data) =>
{
MarketReceivedData = data;
InvokeAsync(StateHasChanged);
});
}
public async Task Dispose()
{
await hubConn.DisposeAsync();
}
async Task generateLineChartTask()
{
MarketDataListener("marketLineChart");
ReceivedMarketDataListener();
await service.GetMarketDataAsync();
}
async Task generateBarChartTask()
{
MarketDataListener("marketBarChart");
ReceivedMarketDataListener();
await service.GetMarketDataAsync();
}
}
FULL CODE: https://github.com/maheshsabnis/SignalRChartBlazor

In the signalr application, opening the page in the browser will generate a new connectionId, which is the default behavior.
We can maintain the ConnectionIds of each user through the following sample code.
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Connections.Features;
using Microsoft.AspNetCore.SignalR;
using System.Collections.Concurrent;
using System.Diagnostics;
namespace SignalRMiddleawre.Hubs
{
/// <summary>
/// </summary>
[Authorize]
public partial class MainHub : Hub
{
#region Connection
/// <summary>
/// Manage Connected Users
/// </summary>
private static ConcurrentDictionary<string?, List<string>>? ConnectedUsers = new ConcurrentDictionary<string?, List<string>>();
/// <summary>
/// OnConnect Event
/// </summary>
/// <param name="userid"></param>
/// <returns></returns>
///
public override async Task OnConnectedAsync()
{
// Get HttpContext In asp.net core signalr
//IHttpContextFeature hcf = (IHttpContextFeature)this.Context.Features[typeof(IHttpContextFeature)];
//HttpContext hc = hcf.HttpContext;
//string uid = hc.Request.Path.Value.Split(new string[] { "=", "" }, StringSplitOptions.RemoveEmptyEntries)[1].ToString();
string? userid = Context.User?.Identity?.Name;
if (userid == null || userid.Equals(string.Empty))
{
Trace.TraceInformation("user not loged in, can't connect signalr service");
return;
}
Trace.TraceInformation(userid + "connected");
// save connection
List<string>? existUserConnectionIds;
ConnectedUsers.TryGetValue(userid, out existUserConnectionIds);
if (existUserConnectionIds == null)
{
existUserConnectionIds = new List<string>();
}
existUserConnectionIds.Add(Context.ConnectionId);
ConnectedUsers.TryAdd(userid, existUserConnectionIds);
await Clients.All.SendAsync("ServerInfo", userid, userid + " connected, connectionId = " + Context.ConnectionId);
await base.OnConnectedAsync();
}
/// <summary>
/// OnDisconnected event
/// </summary>
/// <param name="userid"></param>
/// <returns></returns>
public override async Task OnDisconnectedAsync(Exception? exception)
{
string? userid = Context.User?.Identity?.Name;
// save connection
List<string>? existUserConnectionIds;
ConnectedUsers.TryGetValue(userid, out existUserConnectionIds);
existUserConnectionIds.Remove(Context.ConnectionId);
if (existUserConnectionIds.Count == 0)
{
List<string> garbage;
ConnectedUsers.TryRemove(userid, out garbage);
}
await base.OnDisconnectedAsync(exception);
}
#endregion
#region Message
/// <summary>
/// Send msg to all user
/// </summary>
/// <param name="userid"></param>
/// <param name="message"></param>
/// <returns></returns>
public async Task SendMessage(string msgType, string message)
{
await Clients.All.SendAsync("ReceiveMessage", msgType, message);
}
/// <summary>
/// Send msg to user by userid
/// </summary>
/// <param name="connectionId"></param>
/// <param name="message">message format : type-message </param>
/// <returns></returns>
public async Task SendToSingleUser(string userid, string message)
{
List<string>? existUserConnectionIds;
// find all the connectionids by userid
ConnectedUsers.TryGetValue(userid, out existUserConnectionIds);
if (existUserConnectionIds == null)
{
existUserConnectionIds = new List<string>();
}
existUserConnectionIds.Add(Context.ConnectionId);
ConnectedUsers.TryAdd(userid, existUserConnectionIds);
await Clients.Clients(existUserConnectionIds).SendAsync("ReceiveMessage", message);
}
#endregion
}
}

Related

KiteWorks API using HttpWebRequest and MultipartFormDataContent

jsonWebClient.DataContent = new System.Net.Http.MultipartFormDataContent();
ByteArrayContent bytes = new ByteArrayContent(File.ReadAllBytes(data.LocalFile));
bytes.Headers.ContentType = MediaTypeHeaderValue.Parse("multipart/form-data");
jsonWebClient.DataContent.Add(bytes, "file", data.FileName);
jsonWebClient.DataContent.Add(bytes, "name", data.FileName);
jsonWebClient.DataContent.Add(new StringContent($"{Connection.UserId}"), "userId");
jsonWebClient.DataContent.Add(new StringContent($"{data.ParentId}"), "parentId");
jsonWebClient.DataContent.Add(new StringContent($"{fileInfo.CreationTime}"), "created");
jsonWebClient.DataContent.Add(new StringContent($"{fileInfo.LastWriteTime}"), "modified");
jsonWebClient.DataContent.Add(new StringContent($"{DateTime.Now}"), "clientCreated");
jsonWebClient.DataContent.Add(new StringContent($"{DateTime.Now}"), "clientModified");
jsonWebClient.DataContent.Add(new StringContent($"{fileInfo.Length}"), "size");
So I am trying generate a MultipartFormDataContent to convert to a Byte Array or stream. When I do the stream:
Stream stream = _dataContent.ReadAsStreamAsync().Result;
I get a out of memory exception.
When I try to do:
byte[] bytes = _dataContent.ReadAsByteArrayAsync().Result;
or
byte[] bytes = _dataContent.ReadAsByteArrayAsync().GetAwaiter().GetResult();
It just sits there forever and does nothing. What can I do to make it convert to the proper byte array?
Original source [How would I run an async Task method synchronously?][Rachel]How would I run an async Task<T> method synchronously? method-synchronously
public static class AsyncHelpers
{
/// <summary>
/// Execute's an async Task<T> method which has a void return value synchronously
/// </summary>
/// <param name="task">Task<T> method to execute</param>
public static void RunSync(Func<Task> task)
{
var oldContext = SynchronizationContext.Current;
var synch = new ExclusiveSynchronizationContext();
SynchronizationContext.SetSynchronizationContext(synch);
synch.Post(async _ =>
{
try
{
await task();
}
catch (Exception e)
{
synch.InnerException = e;
throw;
}
finally
{
synch.EndMessageLoop();
}
}, null);
synch.BeginMessageLoop();
SynchronizationContext.SetSynchronizationContext(oldContext);
}
/// <summary>
/// Execute's an async Task<T> method which has a T return type synchronously
/// </summary>
/// <typeparam name="T">Return Type</typeparam>
/// <param name="task">Task<T> method to execute</param>
/// <returns></returns>
public static T RunSync<T>(Func<Task<T>> task)
{
var oldContext = SynchronizationContext.Current;
var synch = new ExclusiveSynchronizationContext();
SynchronizationContext.SetSynchronizationContext(synch);
T ret = default(T);
synch.Post(async _ =>
{
try
{
ret = await task();
}
catch (Exception e)
{
synch.InnerException = e;
throw;
}
finally
{
synch.EndMessageLoop();
}
}, null);
synch.BeginMessageLoop();
SynchronizationContext.SetSynchronizationContext(oldContext);
return ret;
}
private class ExclusiveSynchronizationContext : SynchronizationContext
{
private bool done;
public Exception InnerException { get; set; }
readonly AutoResetEvent workItemsWaiting = new AutoResetEvent(false);
readonly Queue<Tuple<SendOrPostCallback, object>> items =
new Queue<Tuple<SendOrPostCallback, object>>();
public override void Send(SendOrPostCallback d, object state)
{
throw new NotSupportedException("We cannot send to our same thread");
}
public override void Post(SendOrPostCallback d, object state)
{
lock (items)
{
items.Enqueue(Tuple.Create(d, state));
}
workItemsWaiting.Set();
}
public void EndMessageLoop()
{
Post(_ => done = true, null);
}
public void BeginMessageLoop()
{
while (!done)
{
Tuple<SendOrPostCallback, object> task = null;
lock (items)
{
if (items.Count > 0)
{
task = items.Dequeue();
}
}
if (task != null)
{
task.Item1(task.Item2);
if (InnerException != null) // the method threw an exeption
{
throw new AggregateException("AsyncHelpers.Run method threw an exception.", InnerException);
}
}
else
{
workItemsWaiting.WaitOne();
}
}
}
public override SynchronizationContext CreateCopy()
{
return this;
}
}
}
With this class I was able to convert it right away without waiting and there was no issues with Out of Memory Exception.

HttpPost with JSON parameter is not working in ASP.NET Core 3

So, I migrated my RestAPI project to ASP.NET Core 3.0 from ASP.NET Core 2.1 and the HttpPost function that previously worked stopped working.
[AllowAnonymous]
[HttpPost]
public IActionResult Login([FromBody]Application login)
{
_logger.LogInfo("Starting Login Process...");
IActionResult response = Unauthorized();
var user = AuthenticateUser(login);
if (user != null)
{
_logger.LogInfo("User is Authenticated");
var tokenString = GenerateJSONWebToken(user);
_logger.LogInfo("Adding token to cache");
AddToCache(login.AppName, tokenString);
response = Ok(new { token = tokenString });
_logger.LogInfo("Response received successfully");
}
return response;
}
Now, the login object has null values for each property. I read here, that
By default, when you call AddMvc() in Startup.cs, a JSON formatter, JsonInputFormatter, is automatically configured, but you can add additional formatters if you need to, for example to bind XML to an object.
Since AddMvc was removed in aspnetcore 3.0, now I feel this is why I am unable to get my JSON object anymore. My Startup class Configure function looks like this:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseAuthentication();
app.UseRouting();
//app.UseAuthorization();
//app.UseMvc(options
// /*routes => {
// routes.MapRoute("default", "{controller=Values}/{action}/{id?}");
//}*/);
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
endpoints.MapRazorPages();
});
}
The request I am sending through postman (raw and JSON options are selected)
{
"AppName":"XAMS",
"licenseKey": "XAMSLicenseKey"
}
UPDATES
Postman Header: Content-Type:application/json
Startup.cs
public void ConfigureServices(IServiceCollection services)
{
//_logger.LogInformation("Starting Log..."); //shows in output window
services.AddSingleton<ILoggerManager, LoggerManager>();
services.AddMemoryCache();
services.AddDbContext<GEContext>(options => options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
//services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_3_0);
services.AddControllers();
services.AddRazorPages();
//Authentication
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(options =>
{
options.Authority = "https://localhost:44387/";
options.Audience = "JWT:Issuer";
options.TokenValidationParameters.ValidateLifetime = true;
options.TokenValidationParameters.ClockSkew = TimeSpan.FromMinutes(5);
options.RequireHttpsMetadata = false;
});
services.AddAuthorization(options =>
{
options.AddPolicy("GuidelineReader", p => {
p.RequireClaim("[url]", "GuidelineReader");
});
});
//
}
Application.cs
public class Application
{
public string AppName;
public string licenseKey;
}
With you updated code, I think the reason is you didn't create setter for your properties.
To fix the issue, change your Application model as below:
public class Application
{
public string AppName {get;set;}
public string licenseKey {get;set;}
}

.Net Core TestClient cannot post with parameters

I am creating Web API integration test with MSTest Test Server. I can request data. However when I post with data, request routed to Test Server without parameters. Below is my codes, where I am wrong?
/// <summary>
///
/// </summary>
/// <returns></returns>
protected async Task TestDataInitializeAsync()
{
_factory = new WebApplicationFactory<MyWebApi.Startup>();
_client = _factory.CreateClient();
_client.DefaultRequestHeaders.Clear();
_client.DefaultRequestHeaders.Accept.Clear();
_client.DefaultRequestHeaders.Accept.Add(
new MediaTypeWithQualityHeaderValue("application/json"));
_client.DefaultRequestHeaders.Add("User-Agent", ".NET Foundation Repository Reporter");
var content = new FormUrlEncodedContent(new[]
{
new KeyValuePair<string, string>("username", "userABC"),
new KeyValuePair<string, string>("password","password123")
});
var response = await _client.PostAsync("/account/login", content);
}
Here is my Controller method:
/// <summary>
/// Account controller ASP.NET identity authentication
/// </summary>
[Produces("application/json")]
[Route("[controller]")]
[ApiController]
[Authorize]
public class AccountController : ControllerBase
{
[HttpPost]
[AllowAnonymous]
[Route("login")]
public async Task<object> Login(string userName, string password)
{
try
{
var result = await _signInManager.PasswordSignInAsync(userName, password, false, false);
if (result.Succeeded)
{
var appUser = _userManager.Users.SingleOrDefault(r => r.UserName == userName);
return GenerateJwtToken(userName, appUser);
}
else
{
return BadRequest(new { message = "No user found! Please check user name and password." });
}
}
catch (Exception ex)
{
return StatusCode(Microsoft.AspNetCore.Http.StatusCodes.Status500InternalServerError, ex.Message);
}
}
}
}
Parameters, username and password always null.
You should edit your controller's action to get post parameters from your request's body using FromBody :
[HttpPost]
[AllowAnonymous]
[Route("login")]
public async Task<object> Login([FromBody]LoginVm model)
{
...
}
With the LoginVm class containing Username and Password as string properties.
Hope it helps.
I know the reason after read ASP.NET Core 2.2 Microsoft Document carefully. The key point is [ApiController] attribute. It does not related with TestClient.
In .NET Core 2.2, if you put [ApiController] attribute in Class level or Assembly level, .NET Core framework will do parameter binding automatically. We no need to define as [FromBody] in front of the object parameters for complex type. However, for simple type such as int, string, bool, we need to put [FromBody], [FromForm], [FromQuery], [FromHeader], [FromRoute], [FromServices], accordingly.
In my case, I just put [FromFrom] attributes in front of two simple type parameters as below and problem was solved.
<summary>
/// To log-in to the system by using userName and password.
/// If authentication is successed, bearer token will response from server.
/// </summary>
/// <param name="userName"></param>
/// <param name="password"></param>
/// <returns>Bearer Token</returns>
/// <remarks>
/// </remarks>
[HttpPost]
[AllowAnonymous]
[Route("login")]
public async Task<object> Login([FromForm] string userName, [FromForm] string password)
{
try
{
var result = await _signInManager.PasswordSignInAsync(userName, password, false, false);
if (result.Succeeded)
{
var appUser = _userManager.Users.SingleOrDefault(r => r.UserName == userName);
return GenerateJwtToken(userName, appUser);
}
else
{
return BadRequest(new { message = "No user found! Please check user name and password." });
}
}
catch (Exception ex)
{
return StatusCode(Microsoft.AspNetCore.Http.StatusCodes.Status500InternalServerError, ex.Message);
}
}

WcfFacility and Sequence contains no elements error?

I have wcf library with service contracts and implementations.
[ServiceContract]
public interface IServiceProtoType
{
[OperationContract]
Response GetMessage(Request request);
[OperationContract]
String SayHello();
}
[DataContract]
public class Request
{
private string name;
[DataMember]
public string Name
{
get { return name; }
set { name = value; }
}
}
[DataContract]
public class Response
{
private string message;
[DataMember]
public string Message
{
get { return message; }
set { message = value; }
}
}
public class MyDemoService : IServiceProtoType
{
public Response GetMessage(Request request)
{
var response = new Response();
if (null == request)
{
response.Message = "Error!";
}
else
{
response.Message = "Hello, " + request.Name;
}
return response;
}
public string SayHello()
{
return "Hello, World!";
}
}
I have windows service project that references this library, where MyService is just an empty shell that inherits ServiceBase. This service is installed and running under local system.
static void Main()
{
ServiceBase.Run(CreateContainer().Resolve());
}
private static IWindsorContainer CreateContainer()
{
IWindsorContainer container = new WindsorContainer();
container.Install(FromAssembly.This());
return container;
}
public class ServiceInstaller : IWindsorInstaller
{
#region IWindsorInstaller Members
public void Install(IWindsorContainer container, Castle.MicroKernel.SubSystems.Configuration.IConfigurationStore store)
{
string myDir;
if (string.IsNullOrEmpty(AppDomain.CurrentDomain.RelativeSearchPath))
{
myDir = AppDomain.CurrentDomain.BaseDirectory;
}
else
{
myDir = AppDomain.CurrentDomain.RelativeSearchPath;
}
var wcfLibPath = Path.Combine(myDir , "WcfDemo.dll");
string baseUrl = "http://localhost:8731/DemoService/{0}";
AssemblyName myAssembly = AssemblyName.GetAssemblyName(wcfLibPath);
container
.Register(
AllTypes
.FromAssemblyNamed(myAssembly.Name)
.InSameNamespaceAs<WcfDemo.MyDemoService>()
.WithServiceDefaultInterfaces()
.Configure(c =>
c.Named(c.Implementation.Name)
.AsWcfService(
new DefaultServiceModel()
.AddEndpoints(WcfEndpoint
.BoundTo(new WSHttpBinding())
.At(string.Format(baseUrl,
c.Implementation.Name)
)))), Component.For<ServiceBase>().ImplementedBy<MyService>());
}
#endregion
}
In Client Console app I have the following code and I am getting the following error:
{"Sequence contains no elements"}
static void Main(string[] args)
{
IWindsorContainer container = new WindsorContainer();
string baseUrl = "http://localhost:8731/DemoService/{0}";
container.AddFacility<WcfFacility>(f => f.CloseTimeout = TimeSpan.Zero);
container
.Register(
Types
.FromAssemblyContaining<IServiceProtoType>()
.InSameNamespaceAs<IServiceProtoType>()
.Configure(
c => c.Named(c.Implementation.Name)
.AsWcfClient(new DefaultClientModel
{
Endpoint = WcfEndpoint
.BoundTo(new WSHttpBinding())
.At(string.Format(baseUrl,
c.Name.Substring(1)))
})));
var service1 = container.Resolve<IServiceProtoType>();
Console.WriteLine(service1.SayHello());
Console.ReadLine();
}
I have an idea what this may be but you can stop reading this now (and I apologize for wasting your time in advance) if the answer to the following is no:
Is one (or more) of Request, Response, or MyDemoService in the same namespace as IServiceProtoType?
I suspect that Windsor is getting confused about those, since you are doing...
Types
.FromAssemblyContaining<IServiceProtoType>()
.InSameNamespaceAs<IServiceProtoType>()
... and then configuring everything which that returns as a WCF client proxy. This means that it will be trying to create proxies for things that should not be and hence a Sequence Contains no Elements exception (not the most useful message IMHO but crushing on).
The simple fix would be just to put your IServiceProtoType into its own namespace (I often have a namespace like XXXX.Services for my service contracts).
If that is not acceptable to you then you need to work out another way to identify just the service contracts - take a look at the If method for example or just a good ol' Component.For perhaps.

Ehcache hangs in test

I am in the process of rewriting a bottle neck in the code of the project I am on, and in doing so I am creating a top level item that contains a self populating Ehcache. I am attempting to write a test to make sure that the basic call chain is established, but when the test executes it hands when retrieving the item from the cache.
Here are the Setup and the test, for reference mocking is being done with Mockito:
#Before
public void SetUp()
{
testCache = new Cache(getTestCacheConfiguration());
recordingFactory = new EntryCreationRecordingCache();
service = new Service<Request, Response>(testCache, recordingFactory);
}
#Test
public void retrievesResultsFromSuppliedCache()
{
ResultType resultType = mock(ResultType.class);
Response expectedResponse = mock(Response.class);
addToExpectedResults(resultType, expectedResponse);
Request request = mock(Request.class);
when(request.getResultType()).thenReturn(resultType);
assertThat(service.getResponse(request), sameInstance(expectedResponse));
assertTrue(recordingFactory.requestList.contains(request));
}
private void addToExpectedResults(ResultType resultType,
Response response) {
recordingFactory.responseMap.put(resultType, response);
}
private CacheConfiguration getTestCacheConfiguration() {
CacheConfiguration cacheConfiguration = new CacheConfiguration("TEST_CACHE", 10);
cacheConfiguration.setLoggingEnabled(false);
return cacheConfiguration;
}
private class EntryCreationRecordingCache extends ResponseFactory{
public final Map<ResultType, Response> responseMap = new ConcurrentHashMap<ResultType, Response>();
public final List<Request> requestList = new ArrayList<Request>();
#Override
protected Map<ResultType, Response> generateResponse(Request request) {
requestList.add(request);
return responseMap;
}
}
Here is the ServiceClass
public class Service<K extends Request, V extends Response> {
private Ehcache cache;
public Service(Ehcache cache, ResponseFactory factory) {
this.cache = new SelfPopulatingCache(cache, factory);
}
#SuppressWarnings("unchecked")
public V getResponse(K request)
{
ResultType resultType = request.getResultType();
Element cacheEntry = cache.get(request);
V response = null;
if(cacheEntry != null){
Map<ResultType, Response> resultTypeMap = (Map<ResultType, Response>) cacheEntry.getValue();
try{
response = (V) resultTypeMap.get(resultType);
}catch(NullPointerException e){
throw new RuntimeException("Result type not found for Result Type: " + resultType);
}catch(ClassCastException e){
throw new RuntimeException("Incorrect Response Type for Result Type: " + resultType);
}
}
return response;
}
}
And here is the ResponseFactory:
public abstract class ResponseFactory implements CacheEntryFactory{
#Override
public final Object createEntry(Object request) throws Exception {
return generateResponse((Request)request);
}
protected abstract Map<ResultType,Response> generateResponse(Request request);
}
After wrestling with it for a while, I discovered that the cache wasn't being initialized. Creating a CacheManager and adding the cache to it resolved the problem.
I also had a problem with EHCache hanging, although only in a hello-world example. Adding this to the end fixed it (the application ends normally).
CacheManager.getInstance().removeAllCaches();
https://stackoverflow.com/a/20731502/2736496