Orchard CMS WCF Service with ServiceRoute - web-services

I have configured a Orchard module to expose a service and have enabled it. I cannot work out the URL to use based on the following.
Routes.cs
namespace OrchardRestService
{
using System.Collections.Generic;
using System.ServiceModel.Activation;
using Orchard.Mvc.Routes;
using Orchard.Wcf;
public class Routes : IRouteProvider
{
#region Implementation of IRouteProvider
public IEnumerable<RouteDescriptor> GetRoutes()
{
return new[] {
new RouteDescriptor {
Priority = 20,
Route = new ServiceRoute(
"ContentService",
new OrchardServiceHostFactory(),
typeof(IContentService))
}
};
}
public void GetRoutes(ICollection<RouteDescriptor> routes)
{
foreach (var routeDescriptor in GetRoutes())
routes.Add(routeDescriptor);
}
#endregion
}
}
IContentService.cs:
namespace OrchardRestService
{
using System.ServiceModel;
using Orchard;
[ServiceContract]
public interface IContentService : IDependency
{
[OperationContract]
ContentResult GetContent(string contentPath);
}
}
ContentService.cs:
namespace OrchardRestService
{
using System.Collections.Generic;
using System.ServiceModel.Activation;
[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
public class ContentService : IContentService
{
public ContentResult GetContent(string contentPath)
{
var contentResult = new ContentResult
{ ContentValues = new Dictionary<string, string>(), Found = true, Path = contentPath };
return contentResult;
}
}
}
I've tried to follow what Bertrand Le Roy has written here and here but seem to be missing something.
My code is .Net 4 by the way so no need for an SVC file.

Closing as I'd be jumping through hoops to use N2 in this way rather than what it is designed for.

Related

Unit testing memory leaks using xUnit

I recently read this article and it was all about finding, fixing and avoiding memory leaks. I had a feeling that my ASP.NET Core 3.1 app was leaking because it kept increasing the memory usage for the first few minutes but after I kept it opened for like 5 minutes, it sticked to 350 MB in the Diagnostic Tools in Visual Studio 2019. Everything is probably okay because I usually don't forget to dispose stuff (by using block) but I wanted to make sure because my web app is listening to web sockets event for lifetime unless a CancellationToken is requested by the user.
In that article, I saw that it's possible to test an object for memory leak.
[Test]
void MemoryLeakTest()
{
var weakRef = new WeakReference(leakyObject)
// Ryn an operation with leakyObject
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
Assert.IsFalse(weakRef.IsAlive);
}
Can the following code be tested for memory leaks in unit testing using xUnit? More specifically, timer and _subscription.
using Binance.Entity;
using Binance.Extensions;
using Binance.Helpers;
using Binance.Interfaces;
using Binance.Models.Redis;
using Binance.Net;
using Binance.Net.Interfaces;
using Binance.Net.Objects;
using CryptoExchange.Net.Authentication;
using CryptoExchange.Net.Sockets;
using Hangfire;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace Binance.Services
{
public interface IBotService
{
void RunStart(Bot bot);
Task StopAsync();
}
public class BotService : IBotService, IDisposable
{
private readonly ILogger _logger;
private readonly IConfiguration _configuration;
private readonly IBinanceClient _client;
private readonly IBinanceSocketClient _socketClient;
private readonly INotificationService _notificationService;
public BotService(
ILogger<BotService> logger,
IConfiguration configuration,
IBinanceClient client,
IBinanceSocketClient socketClient,
INotificationService notificationService)
{
_logger = logger;
_configuration = configuration;
_client = client;
_socketClient = socketClient;
_notificationService = notificationService;
}
private void ApplyCredentialsOnClient()
{
BinanceClient.SetDefaultOptions(new BinanceClientOptions()
{
ApiCredentials = new ApiCredentials(_configuration.GetValue<string>("BinanceConfig:ApiKey"), _configuration.GetValue<string>("BinanceConfig:SecretKey")),
AutoTimestamp = true,
AutoTimestampRecalculationInterval = TimeSpan.FromMinutes(30)
});
BinanceSocketClient.SetDefaultOptions(new BinanceSocketClientOptions()
{
ApiCredentials = new ApiCredentials(_configuration.GetValue<string>("BinanceConfig:ApiKey"), _configuration.GetValue<string>("BinanceConfig:SecretKey")),
AutoReconnect = true,
ReconnectInterval = TimeSpan.FromHours(24)
});
}
private UpdateSubscription _subscription;
public void RunStart(Bot bot)
{
BackgroundJob.Enqueue(() => Start(bot));
}
public void Start(Bot bot)
{
ApplyCredentialsOnClient();
bool locked = false;
Timer timer = null;
BinanceKline lastKnownKline = null;
_subscription = _socketClient.SubscribeToKlineUpdates(bot.CryptoPair.Symbol, bot.TimeInterval.Interval, async data =>
{
if (data.Data.Final)
{
lastKnownKline = data.Data.ToKline();
// Static logic
// Clean up
if (locked)
{
locked = false;
timer?.Dispose();
}
}
// This block won't be executed before the static block is executed first
else if (lastKnownKline != null && lastKnownKline.Close != data.Data.Close)
{
lastKnownKline = data.Data.ToKline();
if (!locked)
{
locked = true;
timer = new Timer(async state =>
{
// Real time logic
bool condition = true;
if (condition)
{
timer?.Change(Timeout.Infinite, 0);
}
}, null, TimeSpan.Zero, TimeSpan.FromSeconds(scheduledTime));
}
}
}).Data;
}
public async Task StopAsync()
{
await _socketClient.Unsubscribe(_subscription);
}
private bool _disposed = false;
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (_disposed)
return;
if (disposing)
{
_client.Dispose();
}
_disposed = true;
}
}
}

Integrated Unit Testing is not running in ASP.NET core MVC/Web API

I am developing a web API using ASP.Net core. I am doing integrated testing to my project. I am following this link, https://koukia.ca/integration-testing-in-asp-net-core-2-0-51d14ede3968. This is my code.
I have the controller to be tested in the thegoodyard.api project.
namespace thegoodyard.api.Controllers
{
[Produces("application/json")]
[Route("api/category")]
public class CategoryController: Controller
{
[HttpGet("details/{id}")]
public string GetCategory(int id = 0)
{
return "This is the message: " + id.ToString();
}
}
}
I added a new unit test project called thegoodyard.tests to the solution. I added a TestServerFixture class with the following definition
namespace thegoodyard.tests
{
public class TestServerFixture : IDisposable
{
private readonly TestServer _testServer;
public HttpClient Client { get; }
public TestServerFixture()
{
var builder = new WebHostBuilder()
.UseContentRoot(GetContentRootPath())
.UseEnvironment("Development")
.UseStartup<Startup>(); // Uses Start up class from your API Host project to configure the test server
_testServer = new TestServer(builder);
Client = _testServer.CreateClient();
}
private string GetContentRootPath()
{
var testProjectPath = PlatformServices.Default.Application.ApplicationBasePath;
var relativePathToHostProject = #"..\..\..\..\..\..\thegoodyard.api";
return Path.Combine(testProjectPath, relativePathToHostProject);
}
public void Dispose()
{
Client.Dispose();
_testServer.Dispose();
}
}
}
Then again in the test project, I created a new class called, CategoryControllerTests with the following definition.
namespace thegoodyard.tests
{
public class CategoryControllerTests: IClassFixture<TestServerFixture>
{
private readonly TestServerFixture _fixture;
public CategoryControllerTests(TestServerFixture fixture)
{
_fixture = fixture;
}
[Fact]
public async Task GetCategoryDetai()
{
var response = await _fixture.Client.GetAsync("api/category/details/3");
response.EnsureSuccessStatusCode();
var responseString = await response.Content.ReadAsStringAsync();
bool containMessage = false; //responseString.Contains("This is the message: 3"); - I commented on purpose to make the test fails.
Assert.True(containMessage);
}
}
}
Then I right on the test method and clicked run tests in the option to run the test. But none of the tests was run. This is the output.
What is missing in my code? How can I get my integrated test running?
Please check following NuGet packages in your project:
Microsoft.AspNetCore.TestHost
Microsoft.NET.Test.Sdk
xunit
xunit.runner.visualstudio
Perhaps there's a build error that's preventing the project from being compiled. There's really not enough information here to say for sure. Rebuild your solution, and ensure there's no errors.
Aside from that, you can remove some variables by reducing the test code needed. ASP.NET Core includes a WebApplicationFactory<TEntryPoint> fixture out of the box for bootstrapping a test server. You can therefore change your test code to just:
public class CategoryControllerTests: IClassFixture<WebApplicationFactory<Startup>>
{
private readonly WebApplicationFactory<Startup> _factory;
public CategoryControllerTests(WebApplicationFactory<Startup> factory)
{
_factory = factory;
}
[Fact]
public async Task GetCategoryDetail()
{
var client = _factory.CreateClient();
var response = await client.GetAsync("api/category/details/3");
...
See the documentation for additional information and more advanced scenarios.
This way works fine for xUnit based intergration tests, which use Startup configuration. the code blow also demonstrates how to override some settings in appSetting.json to specific values for testing, as well as how to access to DI services.
using System;
using System.Net.Http;
using MyNamespace.Web;
using MyNamespace.Services;
using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.TestHost;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
namespace MyNamespace.Tests
{
public class TestServerDependent : IDisposable
{
private readonly TestServerFixture _fixture;
public TestServer TestServer => _fixture.Server;
public HttpClient Client => _fixture.Client;
public TestServerDependent()
{
_fixture = new TestServerFixture();
var myService = GetService<IMyService>();
// myService.PerformAnyPreparationsForTests();
}
protected TService GetService<TService>()
where TService : class
{
return _fixture.GetService<TService>();
}
public void Dispose()
{
_fixture?.Dispose();
}
}
public class TestServerFixture : IDisposable
{
public TestServer Server { get; }
public HttpClient Client { get; }
public TestServerFixture()
{
var hostBuilder = WebHost.CreateDefaultBuilder()
.ConfigureAppConfiguration(
(builderContext, config) =>
{
var env = builderContext.HostingEnvironment;
config
.AddJsonFile("appsettings.json", optional: false)
.AddJsonFile("appsettings.Testing.json", optional: false,
reloadOnChange: true);
})
.ConfigureLogging(
(hostingContext, logging) =>
{
logging.AddConfiguration(hostingContext.Configuration.GetSection("Logging"));
logging.AddConsole();
logging.AddDebug();
})
.UseStartup<Startup>();
Server = new TestServer(hostBuilder);
Client = Server.CreateClient();
}
public void Dispose()
{
Server.Dispose();
Client.Dispose();
}
public TService GetService<TService>()
where TService : class
{
return Server?.Host?.Services?.GetService(typeof(TService)) as TService;
}
}
}
How the simple integration test might look like with the described above:
using System.Net;
using Xunit;
namespace MyNamespace.Tests
{
public class SimpleIntegrationTest : TestServerDependent
{
[Fact]
public void RedirectToLoginPage()
{
var httpResponseMessage = Client.GetAsync("/").Result;
// Smoke test to make sure you are redirected (to Login page for instance)
Assert.Equal(HttpStatusCode.Redirect, httpResponseMessage.StatusCode);
}
}
}

In SOAP message to WCF web service, how to put KeyIdentifier directly inside SecurityTokenReference (inline, without using Reference token)

I'm successful in communicating with a WCF service via SoapUI (I was given specification on how to configure it), but I'm having trouble in copying those settings to .NET application. Turns out the shape of generated SOAP message (peeked via Fiddler) is being rejected by the web service, who expects a stricter layout of envelope.
I'm very close. On this picture...
... you can see three SOAP messages:
1. With X509SecurityTokenParameters.InclusionMode set to AlwaysToRecipient
2. With X509SecurityTokenParameters.InclusionMode set to Never
3. Expected security token, tested on SoapUI.
How do I achieve envelope from point 3 using C# code? I'm not using app.config file, entire config is inside C# code (but I'm not dedicated on keeping it that way, it just happened). Current code:
using System;
using System.Net;
using System.Security.Cryptography.X509Certificates;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.ServiceModel.Security;
using System.ServiceModel.Security.Tokens;
using System.Text;
public class CustomAlgorithmSuite : SecurityAlgorithmSuite
{
public override string DefaultAsymmetricKeyWrapAlgorithm { get { return "http://www.w3.org/2000/09/xmldsig#dsa-sha1"; }}
public override string DefaultAsymmetricSignatureAlgorithm { get { return "http://www.w3.org/2000/09/xmldsig#dsa-sha1"; }}
public override string DefaultCanonicalizationAlgorithm { get { return "http://www.w3.org/2001/10/xml-exc-c14n#"; }}
public override string DefaultDigestAlgorithm { get { return "http://www.w3.org/2000/09/xmldsig#sha1"; }}
public override string DefaultEncryptionAlgorithm { get { return "http://www.w3.org/2001/04/xmlenc#aes256-cbc"; }}
public override int DefaultEncryptionKeyDerivationLength { get { return SecurityAlgorithmSuite.Default.DefaultEncryptionKeyDerivationLength; }}
public override int DefaultSignatureKeyDerivationLength { get { return SecurityAlgorithmSuite.Default.DefaultSignatureKeyDerivationLength; }}
public override int DefaultSymmetricKeyLength { get { return SecurityAlgorithmSuite.Default.DefaultSymmetricKeyLength; }}
public override string DefaultSymmetricKeyWrapAlgorithm { get { return "http://www.w3.org/2000/09/xmldsig#dsa-sha1"; }}
public override string DefaultSymmetricSignatureAlgorithm { get { return "http://www.w3.org/2000/09/xmldsig#dsa-sha1"; }}
public override bool IsAsymmetricKeyLengthSupported(int length) { return true; }
public override bool IsSymmetricKeyLengthSupported(int length) { return true; }
}
class Program
{
static void Main()
{
X509SecurityTokenParameters x509Params = new X509SecurityTokenParameters()
{
X509ReferenceStyle = X509KeyIdentifierClauseType.RawDataKeyIdentifier,
InclusionMode = SecurityTokenInclusionMode.AlwaysToRecipient,
ReferenceStyle = SecurityTokenReferenceStyle.External,
RequireDerivedKeys = false
};
SecurityBindingElement security = new TransportSecurityBindingElement()
{
MessageSecurityVersion = MessageSecurityVersion.WSSecurity10WSTrust13WSSecureConversation13WSSecurityPolicy12BasicSecurityProfile10,
DefaultAlgorithmSuite = new CustomAlgorithmSuite()
};
security.EndpointSupportingTokenParameters.Endorsing.Add(x509Params);
security.SetKeyDerivation(false);
//security.IncludeTimestamp = false;
TextMessageEncodingBindingElement encoding = new TextMessageEncodingBindingElement(MessageVersion.Soap11, Encoding.UTF8);
HttpsTransportBindingElement transport = new HttpsTransportBindingElement();
//transport.RequireClientCertificate = true;
CustomBinding customBinding = new CustomBinding(security, encoding, transport);
ServicePointManager.ServerCertificateValidationCallback = (a, b, c, d) => true;
var twoCertificatesInOneFile = new X509Certificate2Collection();
twoCertificatesInOneFile.Import("foo path", "foo cert pass", X509KeyStorageFlags.Exportable);
someGeneratedServiceClass client = new someGeneratedServiceClass(customBinding, new EndpointAddress(new Uri("foo webservice address"), EndpointIdentity.CreateDnsIdentity(twoCertificatesInOneFile[0].FriendlyName)));
client.ClientCredentials.ServiceCertificate.DefaultCertificate = twoCertificatesInOneFile[0];
client.ClientCredentials.ClientCertificate.Certificate = twoCertificatesInOneFile[1];
//client.Endpoint.Contract.ProtectionLevel = System.Net.Security.ProtectionLevel.None;
client.ClientCredentials.UserName.UserName = "foo user";
client.ClientCredentials.UserName.Password = "foo pass";
client.someServiceCall("foo", "foo", false, out i1, out i2);
}
}
I ended up using InclusionMode = SecurityTokenInclusionMode.Never, then hijacked the message and replaced incorrect tags manually.
public class CustomProxy_portClient : GeneratedProxy_portClient
{
public CustomProxy_portClient() : base()
{
Endpoint.Behaviors.Remove(typeof(ClientCredentials));
Endpoint.Behaviors.Add(new CustomClientCredentials());
}
}
class CustomClientCredentials : ClientCredentials
{
public CustomClientCredentials() : base() { }
public CustomClientCredentials(ClientCredentials ClientCredentials) : base(ClientCredentials) { }
public override SecurityTokenManager CreateSecurityTokenManager()
{
return new CustomSecurityTokenManager(this);
}
protected override ClientCredentials CloneCore()
{
return new CustomClientCredentials(this);
}
}
class CustomSecurityTokenManager : ClientCredentialsSecurityTokenManager
{
public CustomSecurityTokenManager(ClientCredentials clientCredentials) : base(clientCredentials) { }
public override SecurityTokenSerializer CreateSecurityTokenSerializer(SecurityTokenVersion version)
{
return new CustomWSSecurityTokenSerializer();
}
}
class CustomWSSecurityTokenSerializer : WSSecurityTokenSerializer
{
protected override void WriteKeyIdentifierClauseCore(XmlWriter writer, SecurityKeyIdentifierClause keyIdentifierClause)
{
string xml;
using(MemoryStream ms = new MemoryStream())
{
XmlTextWriter tempWriter = new XmlTextWriter(ms, new UTF8Encoding(false));
base.WriteKeyIdentifierClauseCore(tempWriter, keyIdentifierClause);
xml = Encoding.UTF8.GetString(ms.ToArray());
}
XmlDocument originalKeyIdentifierClause = new XmlDocument();
originalKeyIdentifierClause.LoadXml(xml);
writer.WriteStartElement("SecurityTokenReference");
writer.WriteElementString("KeyIdentifier", originalKeyIdentifierClause.InnerText);
writer.WriteEndElement();
}
}

Why is my programatically added FaultContract not recognized?

I try to have my WCF services always throw detailed faults, even when not throwing them explicitly. To achieve it, I implemented:
an ErrorHandler, whose IErrorHandler.ProvideFault wraps the non-fault error as FaultException
a ServiceBehavior extension, attaching this handler AND adding to each operation a fault description of this FaultException, so the client might catch it as such.
I've decorated my service with the error handler attribute (originally I had two distinct implementations of IServiceBehavior, for the ErrorHandler and for the Operation.Faults).
I also made sure the data set into the new FaultDescription is identical to the one I inspected when defining the FaultContract on my contract.
No matter what I try, when using the FaultContract as attribute on my contract, the fault is being properly caught by the client, but when having it attached at runtime through the ApplyDispatchBehavior, only a general FaultException is being caught. Apparently, everything else (error wrapping and throwing) is working, only the addition of a FaultContract to each action at runtime fails.
Please help...
here's the code:
ErrorHandling.cs
using System;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.ServiceModel.Description;
using System.ServiceModel.Dispatcher;
using System.Text;
using Shared.Contracts.Faults;
namespace Server.WcfExtensions
{
public class MyErrorHandler : IErrorHandler
{
#region IErrorHandler Members
public bool HandleError(Exception error)
{
return false;
}
public void ProvideFault(Exception error, MessageVersion version, ref Message fault)
{
if (error is FaultException) return;
if (!error.GetType().IsSerializable) return;
FaultException<GeneralServerFault> faultExc = new FaultException<GeneralServerFault>(new GeneralServerFault(error), new FaultReason("Server Level Error"));
MessageFault messageFault = faultExc.CreateMessageFault();
fault = Message.CreateMessage(version, messageFault, faultExc.Action);
}
#endregion
}
class ErrorHandler : Attribute, IServiceBehavior
{
Type M_ErrorHandlerType;
public Type ErrorHandlerType
{
get { return M_ErrorHandlerType; }
set { M_ErrorHandlerType = value; }
}
#region IServiceBehavior Members
public void AddBindingParameters(ServiceDescription serviceDescription, System.ServiceModel.ServiceHostBase serviceHostBase, System.Collections.ObjectModel.Collection<ServiceEndpoint> endpoints, System.ServiceModel.Channels.BindingParameterCollection bindingParameters)
{
}
public void ApplyDispatchBehavior(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
{
IErrorHandler errorHandler;
try
{
errorHandler = (IErrorHandler)Activator.CreateInstance(ErrorHandlerType);
}
catch (MissingMethodException e)
{
throw new ArgumentException("Must have a public empty constructor.", e);
}
catch (InvalidCastException e)
{
throw new ArgumentException("Must implement IErrorHandler.", e);
}
foreach (ChannelDispatcherBase channelDispatcherBase in serviceHostBase.ChannelDispatchers)
{
ChannelDispatcher channelDispatcher = channelDispatcherBase as ChannelDispatcher;
channelDispatcher.ErrorHandlers.Add(errorHandler);
}
foreach (ServiceEndpoint ep in serviceDescription.Endpoints)
{
foreach (OperationDescription opDesc in ep.Contract.Operations)
{
Type t = typeof(GeneralServerFault);
string name = t.Name;
FaultDescription faultDescription = new FaultDescription(ep.Contract.Namespace + "/" + ep.Contract.Name + "/" + opDesc.Name + name + "Fault");
faultDescription.Name = name + "Fault";
faultDescription.Namespace = ep.Contract.Namespace;
faultDescription.DetailType = t;
opDesc.Faults.Add(faultDescription);
}
}
}
public void Validate(ServiceDescription serviceDescription, System.ServiceModel.ServiceHostBase serviceHostBase)
{
}
#endregion
}
}
GeneralServerFault.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.Text;
using System.Threading.Tasks;
namespace Shared.Contracts.Faults
{
[DataContract] //[Serializable]
public class GeneralServerFault
{
[DataMember]
public SerializableException Wrapped
{
get;
private set;
}
public GeneralServerFault()
: base()
{
Wrapped = new SerializableException();
}
public GeneralServerFault(Exception toWrap)
: base()
{
Wrapped = new SerializableException(toWrap);
}
}
[Serializable]
public class SerializableException
{
public string Type { get; set; }
public DateTime TimeStamp { get; set; }
public string Message { get; set; }
public string StackTrace { get; set; }
public SerializableException()
{
this.TimeStamp = DateTime.Now;
}
public SerializableException(string Message)
: this()
{
this.Message = Message;
}
public SerializableException(System.Exception ex)
: this(ex.Message)
{
if (ex == null) return;
Type = ex.GetType().ToString();
this.StackTrace = ex.StackTrace;
}
public override string ToString()
{
return this.Type + " " + this.Message + this.StackTrace;
}
}
}
IContractService.cs
using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.ServiceModel;
using Shared.Contracts.Faults;
namespace Shared
{
internal static class Namespaces
{
internal static class Contracts
{
public const string ServiceContracts = "http://mycompany/services";
}
}
[ServiceContract(Namespace = Namespaces.Contracts.ServiceContracts, SessionMode = SessionMode.Required)]
public interface IContactServices
{
[OperationContract]
[FaultContract(typeof(DataNotFoundFault))]
//[FaultContract(typeof(GeneralServerFault))]
void DoSomething();
}
}
ContractService.cs
using System;
using System.Collections.Generic;
using System.ServiceModel;
using Shared;
using Shared.Contracts.Faults;
using Server.WcfExtensions;
namespace Server.Services
{
[ErrorHandler(ErrorHandlerType = typeof(MyErrorHandler))]
public class ContactSevices : IContactServices
{
[OperationBehavior(TransactionScopeRequired = true, TransactionAutoComplete = false)]
public void DoSomething()
{
throw new InvalidCastException("bla");
}
}
}
I omitted the code of client and host

Unit Test a CSLA Asynchronous Validation Rule

I have a validation rule on a CSLA Business Base stereotyped class. I'm having trouble figuring out how to unit test the validation rule as it includes an asynchronous callback lambda expression. Here's some example code:
using System;
using System.Collections.Generic;
using Csla;
using Csla.Validation;
namespace UnitTestCSLAAsyncValidationRule
{
public class BusinessObject : BusinessBase<BusinessObject>
{
protected static PropertyInfo<string> CodeProperty = RegisterProperty<string>(p => p.Code);
public string Code
{
get { return GetProperty(CodeProperty); }
set { SetProperty(CodeProperty, value); }
}
protected override void AddBusinessRules()
{
ValidationRules.AddRule(CodeValidator, new AsyncRuleArgs(CodeProperty));
}
public static void CodeValidator(AsyncValidationRuleContext context)
{
var code = (string) context.PropertyValues["Code"];
CodeList codeList;
CodeList.GetCodeList((o, l) =>
{
codeList = l.Object;
if (codeList.Contains(code))
{
context.OutArgs.Result = false;
context.OutArgs.Description = "Code already in use.";
}
else
{
context.OutArgs.Result = true;
}
});
context.Complete();
}
}
public class CodeList : List<string>
{
public static void GetCodeList(EventHandler<DataPortalResult<CodeList>> handler)
{
DataPortal<CodeList> dp = new DataPortal<CodeList>();
dp.FetchCompleted += handler;
dp.BeginFetch();
}
private void DataPortal_Fetch()
{
// some existing codes..
Add("123");
Add("456");
}
}
}
I would like to test this with a test similar to the following:
using NUnit.Framework;
namespace UnitTestCSLAAsyncValidationRule.Test
{
[TestFixture]
public class BusinessObjectTest
{
[Test]
public void CodeValidationTest()
{
var bo = new BusinessObject();
bo.Code = "123";
Assert.IsNotEmpty(bo.BrokenRulesCollection);
}
}
}
However, the test Assert runs before the async callback. Is this something UnitDriven could help with? I've had a look at it but can't see how to use it in this scenario.
Thanks,
Tom
Answered by JonnyBee on http://forums.lhotka.net/forums/p/10023/47030.aspx#47030:
using NUnit.Framework;
using UnitDriven;
namespace UnitTestCSLAAsyncValidationRule.Test
{
[TestFixture]
public class BusinessObjectTest : TestBase
{
[Test]
public void CodeValidationTest()
{
UnitTestContext context = GetContext();
var bo = new BusinessObject();
bo.ValidationComplete += (o, e) =>
{
context.Assert.IsFalse(bo.IsValid);
context.Assert.Success();
//Assert.IsNotEmpty(bo.BrokenRulesCollection);
};
bo.Code = "123";
context.Complete();
}
}
}
Please not there was a small bug in my validation rule method - the call to AsyncValidationRuleContext.Complete() needs to be inside the lambda.
Thanks,
Tom