I have these methods below that call some SOAP web services, all from the same provider, so they all have the same methods/calls/etc. I'm looking for a more OOP/abstract way to call these without writing so many methods? Ideally I'd like one method for each -> GetClaim(), AddClaim(), SearchClaim(), RemoveClaim(), etc.
Question - Should I pass in the parameters specific to the service to make the method more generic, there by eliminating 15 other methods all like this or is there a better more oop/abstract approach? Can somebody please provide me with an example?
// ex. how can I make these two methods 1?
public async void ClaimSearchForWRG(string url, string userName, string password) {
var client = new WebServiceWRGClient();
var binding = new BasicHttpBinding(BasicHttpSecurityMode.Transport);
var endpoint = new EndpointAddress(url);
var channelFactory = new ChannelFactory<WebServiceWRG>(binding, endpoint);
var webService = channelFactory.CreateChannel();
var user = new User();
user.UserName = await webService.EncryptValueAsync(userName);
user.Password = await webService.EncryptValueAsync(password);
var response = await client.ClaimSearchAsync(user, "", "", 12345, statuscode.NotSet, "");
}
// another call (same provider) with the same call -> ClaimSearchAsync()
public async void ClaimSearchForAWI(string url, string userName, string password) {
var client = new WebServiceAWIClient();
var binding = new BasicHttpBinding(BasicHttpSecurityMode.Transport);
var endpoint = new EndpointAddress(url);
var channelFactory = new ChannelFactory<WebServiceAWI>(binding, endpoint);
var webService = channelFactory.CreateChannel();
var user = new ArmUser();
user.UserName = await webService.EncryptValueAsync(userName);
user.Password = await webService.EncryptValueAsync(password);
var response = await client.ClaimSearchAsync(user, "", "", 12345, ArmStatuscode.NotSet, "");
}
// then we have 15 other web service calls from the same provider for ClaimSearchAsync()
// then we have 15 more calls for ClaimGetAsync()
// then we have 15 more calls for AddClaimAsync()
// then we have 15 more calls for RemoveClaimAsync()
// etc, etc, etc
UPDATED After trying this code below to make things a little more generic (to eliminate redundancy) I'm getting some errors in the code. Specifically related to the compiler not finding the properties associated with the generic entities I'm passing into the method. ex. user.Username is not found -> error message says "'TTwo' does not contain a definition for 'UserName'"
public class Test {
public void TestWebService() {
var ws = new WebService<WebServiceWRG>();
ws.SearchClaim(new WebServiceWRGClient(), new GraceUser(),
"https://trustonline.delawarecpf.com/tows/webservicewrg.svc", "userName", "password");
}
}
public class WebService<T> {
public void SearchClaim<TOne, TTwo>(TOne entity1, TTwo entity2, string url, string userName, string password)
where TOne : class
where TTwo : class
{
var client = entity1;
var binding = new BasicHttpBinding(BasicHttpSecurityMode.Transport);
var endpoint = new EndpointAddress(url);
var channelFactory = new ChannelFactory<T>(binding, endpoint);
var webService = channelFactory.CreateChannel();
var user = entity2;
user.UserName = webService.EncryptValue(userName);
user.Password = webService.EncryptValue(password);
var response = client.ClaimSearch(user, "", "", 12345, GraceStatuscode.NotSet, "");
}
}
UPDATED I was asked to show what "ClaimSearchAsync" does or what it is. I copied this from the web service reference file that was generated from dotnet
System.Threading.Tasks.Task<GRACE_GRACES.WebServiceResult> ClaimSearchAsync(GRACE_GRACES.User user, string ssn, string lastname, int claimnumber, GRACE_GRACES.statuscode statuscode, string assignedto);
as this is a web service, there is no method or code behind that shows what it does.
The provided example methods all violate Single Responsibility Principle (SRP) and Separation of Concerns (SoC) so that is where I started in trying to make them more generic.
The creation of the service and service client should be abstracted out into their own concerns
For example, the web services can be created via a generic factory abstraction
public interface IWebServiceFactory {
TWebService Create<TWebService>(string uri);
}
and simple implementation which encapsulates the creation of the channel factory using the provided URL.
public class ServiceFactory : IWebServiceFactory {
public TWebService Create<TWebService>(string url) {
var binding = new BasicHttpBinding(BasicHttpSecurityMode.Transport) {
MaxReceivedMessageSize = Int32.MaxValue,
MaxBufferSize = Int32.MaxValue
};
var endpoint = new EndpointAddress(url);
var channelFactory = new ChannelFactory<TWebService>(binding, endpoint);
TWebService webService = channelFactory.CreateChannel();
return webService;
}
}
The service clients' creation can also be abstracted out into it own concern.
public interface IClientFactory {
TClient Create<TClient>() where TClient : class, new();
}
to be implemented based on the common definition of your clients.
Now for the creation of a generic client you need to take the common functionality expected from the types involved with the member to be invoked.
This can allow for a convention to be used for the expected types. Dynamic expressions were used to construct the conventions applied.
Resulting in the following helpers for the SearchClaimAsync
static class ExpressionHelpers {
public static Func<string, string, TUserResult> CreateUserDelegate<TUserResult>() {
var type = typeof(TUserResult);
var username = type.GetProperty("username", BindingFlags.Instance | BindingFlags.IgnoreCase | BindingFlags.Public);
var password = type.GetProperty("password", BindingFlags.Instance | BindingFlags.IgnoreCase | BindingFlags.Public);
//string username =>
var usernameSource = Expression.Parameter(typeof(string), "username");
//string password =>
var passwordSource = Expression.Parameter(typeof(string), "password");
// new TUser();
var user = Expression.New(type);
// new TUser() { UserName = username, Password = password }
var body = Expression.MemberInit(user, bindings: new[] {
Expression.Bind(username, usernameSource),
Expression.Bind(password, passwordSource)
});
// (string username, string password) => new TUser() { UserName = username, Password = password }
var expression = Expression.Lambda<Func<string, string, TUserResult>>(body, usernameSource, passwordSource);
return expression.Compile();
}
public static Func<TService, string, Task<string>> CreateEncryptValueDelegate<TService>() {
// (TService service, string name) => service.EncryptValueAsync(name);
var type = typeof(TService);
// TService service =>
var service = Expression.Parameter(type, "service");
// string name =>
var name = Expression.Parameter(typeof(string), "name");
// service.EncryptValueAsync(name)
var body = Expression.Call(service, type.GetMethod("EncryptValueAsync"), name);
// (TService service, string name) => service.EncryptValueAsync(name);
var expression = Expression.Lambda<Func<TService, string, Task<string>>>(body, service, name);
return expression.Compile();
}
public static Func<TClient, TUser, Task<TResponse>> CreateClaimSearchDelegate<TClient, TUser, TResponse>() {
var type = typeof(TClient);
// TClient client =>
var client = Expression.Parameter(type, "client");
// TUser user =>
var user = Expression.Parameter(typeof(TUser), "user");
var method = type.GetMethod("ClaimSearchAsync");
var enumtype = method.GetParameters()[4].ParameterType; //statuscode
var enumDefault = Activator.CreateInstance(enumtype);
var arguments = new Expression[] {
user,
Expression.Constant(string.Empty), //ssn
Expression.Constant(string.Empty), //lastname
Expression.Constant(12345), //claimnumber
Expression.Constant(enumDefault), //statuscode
Expression.Constant(string.Empty)//assignto
};
// client.ClaimSearchAsync(user, ssn: "", lastname: "", claimnumber: 12345, statuscode: default(enum), assignedto: "");
var body = Expression.Call(client, method, arguments);
// (TClient client, TUser user) => client.ClaimSearchAsync(user,....);
var expression = Expression.Lambda<Func<TClient, TUser, Task<TResponse>>>(body, client, user);
return expression.Compile();
}
}
Take some time to review the comments to get a better understanding of what is being done.
The generic web service can then be defined as follows
public class WebService<TWebServiceClient, TWebService, TUser>
where TWebService : class
where TWebServiceClient : class, new()
where TUser : class, new() {
/// <summary>
/// Create user object model
/// </summary>
private static readonly Func<string, string, TUser> createUser =
ExpressionHelpers.CreateUserDelegate<TUser>();
/// <summary>
/// Encrypt provided value using <see cref="TWebService"/>
/// </summary>
private static readonly Func<TWebService, string, Task<string>> encryptValueAsync =
ExpressionHelpers.CreateEncryptValueDelegate<TWebService>();
private readonly IWebServiceFactory serviceFactory;
private readonly IClientFactory clientFactory;
Lazy<TWebServiceClient> client;
public WebService(IWebServiceFactory serviceFactory, IClientFactory clientFactory) {
this.serviceFactory = serviceFactory ?? throw new ArgumentNullException(nameof(serviceFactory));
this.clientFactory = clientFactory ?? throw new ArgumentNullException(nameof(clientFactory));
client = new Lazy<TWebServiceClient>(() => clientFactory.Create<TWebServiceClient>());
}
public async Task<TResponse> SearchClaimAsync<TResponse>(WebServiceOptions options) {
TWebService webService = serviceFactory.Create<TWebService>(options.URL);
TUser user = createUser(
await encryptValueAsync(webService, options.UserName),
await encryptValueAsync(webService, options.Password)
);
Func<TWebServiceClient, TUser, Task<TResponse>> claimSearchAsync =
ExpressionHelpers.CreateClaimSearchDelegate<TWebServiceClient, TUser, TResponse>();
TResponse response = await claimSearchAsync.Invoke(client.Value, user);
return response;
}
//...other generic members to be done
}
public class WebServiceOptions {
public string URL { get; set; }
public string UserName { get; set; }
public string Password { get; set; }
}
The code it self is decoupled enough from implementation concerns to allow for it to be tested in isolation to ensure that it behaves as expected.
As demonstrated in the following unit tested
[TestClass]
public class GenericWebServiceTests {
[TestMethod]
public void Should_Create_New_WebService() {
//Arrange
var serviceFactory = Mock.Of<IWebServiceFactory>();
var clientFactory = Mock.Of<IClientFactory>();
//Act
var actual = new WebService<WebServiceWRGClient, IWebService, User1>(serviceFactory, clientFactory);
//Assert
actual.Should().NotBeNull();
}
[TestMethod]
public async Task Should_ClaimSearchAsync() {
//Arrange
var service = Mock.Of<IWebService>();
Mock.Get(service)
.Setup(_ => _.EncryptValueAsync(It.IsAny<string>()))
.ReturnsAsync((string s) => s);
var serviceFactory = Mock.Of<IWebServiceFactory>();
Mock.Get(serviceFactory)
.Setup(_ => _.Create<IWebService>(It.IsAny<string>()))
.Returns(service);
var clientFactory = Mock.Of<IClientFactory>();
Mock.Get(clientFactory)
.Setup(_ => _.Create<WebServiceWRGClient>())
.Returns(() => new WebServiceWRGClient());
string url = "url";
string username = "username";
string password = "password";
var options = new WebServiceOptions {
URL = url,
UserName = username,
Password = password
};
var webService = new WebService<WebServiceWRGClient, IWebService, User1>(serviceFactory, clientFactory);
//Act
var actual = await webService.SearchClaimAsync<WebServiceResult>(options);
//Assert
//Mock.Get(serviceFactory).Verify(_ => _.Create<IService1>(url));
//Mock.Get(service).Verify(_ => _.EncryptValue(username));
//Mock.Get(service).Verify(_ => _.EncryptValue(password));
//Mock.Get(clientFactory).Verify(_ => _.Create<Client1>());
actual.Should().NotBeNull();
}
#region Support
public class User1 {
public string UserName { get; set; }
public string Password { get; set; }
}
public class User2 {
public string UserName { get; set; }
public string Password { get; set; }
}
public class WebServiceWRGClient {
public Task<WebServiceResult> ClaimSearchAsync(User1 user, string ssn, string lastname, int claimnumber, statuscode statuscode, string assignedto) {
return Task.FromResult(new WebServiceResult());
}
}
public enum statuscode {
NotSet = 0,
}
public class Client2 { }
public interface IWebService {
Task<string> EncryptValueAsync(string value);
}
public interface IService2 {
Task<string> EncryptValueAsync(string value);
}
public class Service1 : IWebService {
public Task<string> EncryptValueAsync(string value) {
return Task.FromResult(value);
}
}
public class WebServiceResult {
}
#endregion
}
This should be enough to get you started in reviewing the other members to be made generic. The above provided code has been tested and works as expected based on what was provided in the original question.
Do note that this does seem like a large task depending on the amount of members to be refactored. You should take some time to make sure the effort is even worth it.
You have classical Divergent Change smell here.
Signs and Symptoms. You find yourself having to change many unrelated methods when you make changes to a class. For example, when adding a new product type you have to change the methods for finding, displaying, and ordering products.
I suggest to make refactoring to Abstract Factory pattern. You will separate web service and object creation logic.
Abstract Factory is a creational design pattern that lets you produce families of related objects without specifying their concrete classes.
So you will have something like:
And some code:
public interface IFactory
{
Client CreateClient();
User CreateUser();
Channel CreateChannel(BasicHttpBinding binding, EndpointAddress endpoint);
}
abstract public class AbstractFactory<T> : IFactory
{
public abstract Client CreateClient()
public abstract User CreateUser();
public Channel CreateChannel(BasicHttpBinding binding, EndpointAddress endpoint)
{
var channelFactory = new ChannelFactory<T>(binding, endpoint);
return channelFactory.CreateChannel();
}
}
public class AWIFactory : AbstractFactory<WebServiceAWI>
{
public override Client CreateClient()
{
return new WebServiceAWIClient();
}
public override User CreateUser()
{
return new ArmUser();
}
}
public class WRGFactory : AbstractFactory<WebServiceWRG>
{
public override Client CreateClient()
{
return new WebServiceWRGClient();
}
public override User CreateUser()
{
return new User();
}
}
public class WebService
{
private readonly IFactory _factory;
public WebService(IFactory factory)
{
_factory = factory;
}
public async void ClaimSearchAsync(string url, string userName, string password)
{
var client = _factory.CreateClient();
var binding = new BasicHttpBinding(BasicHttpSecurityMode.Transport);
var endpoint = new EndpointAddress(url);
var channel = _factory.CreateChannel(binding, endpoint);
var user = _factory.CreateUser();
user.UserName = await channel.EncryptValueAsync(userName);
user.Password = await channel.EncryptValueAsync(password);
var response = await client.ClaimSearchAsync(user, "", "", 12345, statusCode, "");
}
...
}
And here is how you create WebService:
var wrgWebService = new WebService(new WRGFactory());
I did something similar when I had several different soap endpoints, where every endpoint had some types that were completely the same, only with a different class name. Automatically generated classes contain the partial modifier, which enables you to add additional logic to the generated class.
In your case:
"'TTwo' does not contain a definition for 'UserName'"
You have to create an interface that contains a property Username and a property Password:
public interface IUser {
string UserName { get; }
string Password { get; }
}
public partial User : IUser { } //must be in the correct namespace for partial to work
public partial ArmUser : IUser { } //must be in the correct namespace for partial to work
public class Test {
public void TestWebService() {
var ws = new WebService<WebServiceWRG>();
ws.SearchClaim(new WebServiceWRGClient(), new GraceUser(),
"https://trustonline.delawarecpf.com/tows/webservicewrg.svc", "userName", "password");
}
}
public class WebService<T> {
public void SearchClaim<TOne, TTwo>(TOne entity1, TTwo entity2, string url, string userName, string password)
where TOne : class
where TTwo : IUser // limits the TTwo class to implement IUser
{
var client = entity1;
var binding = new BasicHttpBinding(BasicHttpSecurityMode.Transport);
var endpoint = new EndpointAddress(url);
var channelFactory = new ChannelFactory<T>(binding, endpoint);
var webService = channelFactory.CreateChannel();
var user = entity2;
user.UserName = webService.EncryptValue(userName);
user.Password = webService.EncryptValue(password);
var response = client.ClaimSearch(user, "", "", 12345, GraceStatuscode.NotSet, "");
}
}
Instead of passing TTwo, you can then also add the new modifier to the TTwo condition (where T : TTwo, new()), then you can generate an instance of TTwo inside the SearchClaim function, which would make it look like the following:
public interface IUser {
string UserName { get; set; }
string Password { get; set; }
}
public partial User : IUser { } //must be in the correct namespace for partial to work
public partial ArmUser : IUser { } //must be in the correct namespace for partial to work
public class Test {
public void TestWebService() {
var ws = new WebService<WebServiceWRG>();
ws.SearchClaim(new WebServiceWRGClient(), new GraceUser(),
"https://trustonline.delawarecpf.com/tows/webservicewrg.svc", "userName", "password");
}
}
public class WebService<T> {
public void SearchClaim<TOne, TTwo>(TOne entity1, string url, string userName, string password)
where TOne : class
where TTwo : IUser, new() // limits the TTwo class to implement IUser
{
var client = entity1;
var binding = new BasicHttpBinding(BasicHttpSecurityMode.Transport);
var endpoint = new EndpointAddress(url);
var channelFactory = new ChannelFactory<T>(binding, endpoint);
var webService = channelFactory.CreateChannel();
var user = new TTwo();
user.UserName = webService.EncryptValue(userName);
user.Password = webService.EncryptValue(password);
var response = client.ClaimSearch(user, "", "", 12345, GraceStatuscode.NotSet, "");
}
}
You might also have to make some interface for your TOne, but you should be able to figure that out on your own.
Not sure how is your code structure, but I'll just focus on the provided sample.
From what I've seen, if this method and the other related methods are used in different classes, I would suggest to create a class that will handle it, and then use this class instead of the methods. But if this method and other related methods are used in a specific class, I would recommend to create a generic methods that will substitute the redundant methods. You'll need to compare all related methods first, and get the common numerator between them, make this a start point for your generic approach.
Here is untested example (based on what I understood from your sample) :
public class CallWebService<T> // don't forget to inherit IDisposal.
{
private WebServiceWRGClient Client {get; set;}
private BasicHttpBinding HttpBinding {get; set;}
private EndpointAddress Endpoint {get; set;}
private ChannelFactory Channel {get; set;}
// if needed outside this class, make it public to be accessed globally.
private User UserAccount {get; set;}
public CallWebService<T>(string url)
{
Client = new WebServiceWRGClient();
//See which Binding is the default and use it in this constructor.
HttpBinding = new BasicHttpBinding(BasicHttpSecurityMode.Transport);
Endpoint = new EndpointAddress(url);
// T is generic, WebServiceWRG in this example
Channel = new ChannelFactory<T>(HttpBinding, Endpoint).CreateChannel();
UserAccount = new User();
}
// another constructor with BasicHttpBinding
public CallWebService<T>(string url, BasicHttpSecurityMode securityMode)
{
Client = new WebServiceWRGClient();
//See which Binding is the default and use it in this constructor.
HttpBinding = new BasicHttpBinding(securityMode);
Endpoint = new EndpointAddress(url);
// T is generic, WebServiceWRG in this example
Channel = new ChannelFactory<T>(HttpBinding, Endpoint).CreateChannel();
UserAccount = new User();
}
// Change this method to return the response. Task<Response> is just a placeholder for this example
public async Task<Response> Call(string userName, string password)
{
UserAccount.UserName = await Channel.EncryptValueAsync(userName);
UserAccount.Password = await Channel.EncryptValueAsync(password);
var response = await Client.ClaimSearchAsync(User, "", "", 12345, statuscode.NotSet, "");
}
/*
[To-Do] : gather all other releated methods into this class, then try to simplify them.
*/
}
You can also configure the constructors as needed, for instance, you can make constructors that takes WebServiceWRGClient and BasicHttpBinding ..etc. So, it's more open to you.
You could do similar approach if it'll be used across the project, but if it's only used in one class, then you could do something like this :
// Configure it as needed, but avoid using `void` with async, as the exceptions in sync and async methods handled differently.
// Also, try to make sense here, make the method return the results.
public async Task CallWebService<T>(WebServiceWRGClient client, string url, string userName, string password)
{
var channelFactory = new ChannelFactory<T>(new BasicHttpBinding(BasicHttpSecurityMode.Transport, new EndpointAddress(url)).CreateChannel();
var user = new User(); // coming from service reference
user.UserName = await channelFactory.EncryptValueAsync(userName);
user.Password = await channelFactory.EncryptValueAsync(password);
var response = await client.ClaimSearchAsync(user, "", "", 12345, statuscode.NotSet, "");
}
In our MVC4 application with Entity Framework 4.0 based on the Music Store Tutorial we are using Moq to mock the DbContext and unit test are logic. One of our methods proves difficult to test though since it makes use of HttpContext or HttpContextBase. One example method looks like this:
public static ShoppingCart GetCart(HttpContextBase context)
{
var cart = new ShoppingCart();
cart.ShoppingCartId = cart.GetCartId(context);
return cart;
}
The only property collected from HttpContextBase is the [CartSessionKey] as can be seen here:
public string GetCartId(HttpContextBase context)
{
if (context.Session[CartSessionKey] == null)
{
if (!string.IsNullOrWhiteSpace(context.User.Identity.Name))
{
context.Session[CartSessionKey] =
context.User.Identity.Name;
}
else
{
// Generate a new random GUID using System.Guid class
Guid tempCartId = Guid.NewGuid();
// Send tempCartId back to client as a cookie
context.Session[CartSessionKey] = tempCartId.ToString();
}
}
return context.Session[CartSessionKey].ToString();
}
We have heard horror stories that HttpContext is a very complex class and that if you print it you have enough paper to circle the earth eight times.
Nevertheless we want to mock it. The question is how. The properties that we want to mock are the [CartSessionKey], and the property that come from the context as contest.User.Identity.Name.
We suspect we need to use something like this:
var mockData = new Mock<FakeContext>();
mockData.Setup(m => m.Orders).Returns(memoryOrderItems);
mockData.Setup(m => m.Carts).Returns(memoryCartItems);
Mock<HttpContextBase> mockHttpContext = new Mock<HttpContextBase>();
Mock<HttpRequestBase> mockHttpRequest = new Mock<HttpRequestBase>();
mockHttpRequest.Setup(x => x.CartSessionKey).Returns(1);
mockHttpContext.Setup(x => x.Request).Returns(mockHttpRequest.Object);
but we cannot find how to specifically implement this so we do not get any errors on methods that use context.Session[CartSessionKey] or context.User.Identity.Name.
We hope someone can help us out.
/edit
When we do this:
var memoryUserItems = new FakeDbSet<User>()
{
new User { Email = "test#test.de",
FullName = "Test Person",
isAvailable = true,
Name = "WHat"
},
new User { Email = "test2#test.de",
FullName = "Test Person 2",
isAvailable = true,
Name = "WHat 2"
}
};
(...) Other memory...Items
And then this:
// Create mock units of work
var mockData = new Mock<FakeContext>();
mockData.Setup(m => m.Orders).Returns(memoryOrderItems);
mockData.Setup(m => m.Carts).Returns(memoryCartItems);
mockData.Setup(m => m.Users).Returns(memoryUserItems);
var principalMock = new Mock<IPrincipal>();
var identityMock = new Mock<IIdentity>();
var userMock =
identityMock.Setup(x => x.Name).Returns("Test!");
identityMock.Setup(x => x.IsAuthenticated).Returns(true); // optional ;)
mockData.Setup(x => x.Identity).Returns(identityMock.Object);
var httpReqBase = new Mock<HttpRequestBase>(); // this is useful if you want to test Ajax request checks or cookies in the controller.
var httpContextBase = new Mock<HttpContextBase>();
httpContextBase.Setup(x => x.User).Returns(principalMock.Object);
httpContextBase.Setup(x => x.Session[It.IsAny<string>()]).Returns(1); //Here is the session indexer. You can swap 'any' string for specific string.
httpContextBase.Setup(x => x.Request).Returns(httpReqBase.Object);
We get the error that:
Error 3 'project.Models.FakeContext' does
not contain a definition for 'Identity' and no extension method
'Identity' accepting a first argument of type
'project.Models.FakeContext' could be found
(are you missing a using directive or an assembly
reference?)
/ edit2
To make it more clear. The actual method I am testing is the following:
public ActionResult Complete(int id)
{
// Make sure that user is currentuser and otherwise bring user to our Thief page
if (id != db.GetCurrentUserId())
{
return View("Thief");
}
var cart = ShoppingCart.GetCart(this.HttpContext);
var currentDate = DateTime.Today;
var viewModel = new ShoppingCartViewModel
{
CartItems = cart.GetCartItems(),
CartTotal = cart.GetTotal(),
ProductItems = db.Products.ToList()
};
if (viewModel.CartItems.Count() == 0)
{
return View("Empty");
}
// Try to write cart to order table
try
{
foreach (var item in viewModel.CartItems)
{
ProcessOrder(item, id, currentDate);
}
// after this we empty the shopping cart
cart.EmptyCart();
return View();
}
catch
{
// Invalid - display error page
return View("Error");
}
}
As can be seen the var cart = ShoppingCart.GetCart(this.HttpContext); uses this.HttpContext. In the test I just do controller.Complete(1). I cannot pass a new HttpContext to the controller I guess?
/ edit 3
While using the code below with the mocks I get the following message:
Test Name: TestCheckoutCompleteShouldWithEmptyCart
Test FullName: Controllers.CheckoutControllerTest.TestCheckoutCompleteShouldWithEmptyCart
Test Source: Controllers\CheckoutControllerTest.cs : line 141
Test Outcome: Failed
Test Duration: 0:00:00.0158591
Result Message:
Test method Controllers.CheckoutControllerTest.TestCheckoutCompleteShouldWithEmptyCart threw exception:
System.NullReferenceException: Object reference not set to an instance of an object.
Result StackTrace:
at Models\ShoppingCart.cs:line 170
at \Models\ShoppingCart.cs:line 20
at \Controllers\CheckoutController.cs:line 48
at Controllers\CheckoutControllerTest.cs:line 143
OK, here it goes. The following works in MVC5 with AD, I'm not sure if it's fully backwards compatible, you'll have to check.
var principalMock = new Mock<IPrincipal>();
var identityMock = new Mock<IIdentity>();
identityMock.Setup(x => x.Name).Returns("Test!");
identityMock.Setup(x => x.IsAuthenticated).Returns(true); // optional ;)
userMock.Setup(x => x.Identity).Returns(identityMock.Object);
var httpReqBase = new Mock<HttpRequestBase>(); // this is useful if you want to test Ajax request checks or cookies in the controller.
var httpContextBase = new Mock<HttpContextBase>();
httpContextBase.Setup(x => x.User).Returns(principalMock.Object);
httpContextBase.Setup(x => x.Session[It.IsAny<string>()]).Returns(1); //Here is the session indexer. You can swap 'any' string for specific string.
httpContextBase.Setup(x => x.Request).Returns(httpReqBase.Object);
This would help you to write a proper Unit Test using Moq.
[TestClass]
public class SutTest
{
[TestMethod]
public void GetCartId_WhenUserNameIsNotNull_SessionContainsUserName()
{
var httpContextStub = new Mock<HttpContextBase>();
var httpSessionStub = new Mock<ISessionSettings>();
httpSessionStub.Setup(x => x.Get<string>(It.IsAny<string>())).Returns(() => null);
httpSessionStub.SetupSequence(x => x.Get<string>(It.IsAny<string>()))
.Returns(null)
.Returns("FakeName");
var httpUserStub = new Mock<IPrincipal>();
var httpIdenttyStub = new Mock<IIdentity>();
httpUserStub.SetupGet(x => x.Identity).Returns(httpIdenttyStub.Object);
httpIdenttyStub.SetupGet(x => x.Name).Returns("FakeName");
httpContextStub.Setup(x => x.User).Returns(httpUserStub.Object);
var sut = new Sut(httpSessionStub.Object);
var result = sut.GetCartId(httpContextStub.Object);
Assert.AreEqual("FakeName",result );
}
}
Check the SetupSequence method which gives you find Control over different values being return on he same stubbed call.
Also important to decouple your session from HttpContext as you can always run into issues.
public class SessionSettings : ISessionSettings
{
private readonly HttpSessionStateBase _session;
public SessionSettings(HttpSessionStateBase session)
{
_session = session;
}
public T Get<T>(string key)
{
return (T)_session[key];
}
public void Set<T>(string key, T value)
{
_session[key] = value;
}
}
public interface ISessionSettings
{
T Get<T>(string key);
void Set<T>(string key, T value);
}
public class Sut
{
private ISessionSettings _sessionSettings;
public Sut(ISessionSettings sessionSettings)
{
_sessionSettings = sessionSettings;
}
public string GetCartId(HttpContextBase context)
{
if (_sessionSettings.Get<string>(CartSessionKey) == null)
{
if (!string.IsNullOrWhiteSpace(context.User.Identity.Name))
{
_sessionSettings.Set<string>(CartSessionKey, context.User.Identity.Name);
}
else
{
// Generate a new random GUID using System.Guid class
Guid tempCartId = Guid.NewGuid();
// Send tempCartId back to client as a cookie
_sessionSettings.Set<string>(CartSessionKey, tempCartId.ToString());
}
}
return _sessionSettings.Get<string>(CartSessionKey);
}
private string CartSessionKey = "key";
}
This way the code is more readable and easier to understand.
I am attempting to make a Windows Forms application that plugs into some services exposed by ASP.NET MVC WebAPI, but am having a great deal of trouble with the authentication/login part.
I cannot seem to find an example that just demonstrates how to do this from Windows Forms, everything I find seems to be very convoluted and includes a lot of very deep plumbing, or seems targeted to other ASP.NET websites, and not windows forms.
Is there something I am missing? Is this just not possible? Or is it just not intended? I've looked at things like this .NET WebApi Authentication that claim to do it, but I don't see how to use cookies from a Windows Forms standpoint. I've also gone over http://blogs.msdn.com/b/webdev/archive/2012/08/26/asp-net-web-api-and-httpclient-samples.aspx and still have had very little luck.
Just create authentication token on server-side and store it in your database or even in cache. Then send this token with requests from your win forms application. WebApi should check this token all the time. It's good enough and you have full control over your auth process.
Let me share, how it works for me:
Object with Auth details:
public class TokenIdentity
{
public int UserID { get; set; }
public string AuthToken { get; set; }
public ISocialUser SocialUser { get; set; }
}
Web API Auth Controller:
public class AuthController : ApiController
{
public TokenIdentity Post(
SocialNetwork socialNetwork,
string socialUserID,
[FromUri]string socialAuthToken,
[FromUri]string deviceRegistrationID = null,
[FromUri]DeviceType? deviceType = null)
{
var socialManager = new SocialManager();
var user = socialManager.GetSocialUser(socialNetwork, socialUserID, socialAuthToken);
var tokenIdentity = new AuthCacheManager()
.Authenticate(
user,
deviceType,
deviceRegistrationID);
return tokenIdentity;
}
}
Auth Cache Manager:
public class AuthCacheManager : AuthManager
{
public override TokenIdentity CurrentUser
{
get
{
var authToken = HttpContext.Current.Request.Headers["AuthToken"];
if (authToken == null) return null;
if (HttpRuntime.Cache[authToken] != null)
{
return (TokenIdentity) HttpRuntime.Cache.Get(authToken);
}
return base.CurrentUser;
}
}
public int? CurrentUserID
{
get
{
if (CurrentUser != null)
{
return CurrentUser.UserID;
}
return null;
}
}
public override TokenIdentity Authenticate(
ISocialUser socialUser,
DeviceType? deviceType = null,
string deviceRegistrationID = null)
{
if (socialUser == null) throw new ArgumentNullException("socialUser");
var identity = base.Authenticate(socialUser, deviceType, deviceRegistrationID);
HttpRuntime.Cache.Add(
identity.AuthToken,
identity,
null,
DateTime.Now.AddDays(7),
Cache.NoSlidingExpiration,
CacheItemPriority.Default,
null);
return identity;
}
}
Auth Manager:
public abstract class AuthManager
{
public virtual TokenIdentity CurrentUser
{
get
{
var authToken = HttpContext.Current.Request.Headers["AuthToken"];
if (authToken == null) return null;
using (var usersRepo = new UsersRepository())
{
var user = usersRepo.GetUserByToken(authToken);
if (user == null) return null;
return new TokenIdentity
{
AuthToken = user.AuthToken,
SocialUser = user,
UserID = user.ID
};
}
}
}
public virtual TokenIdentity Authenticate(
ISocialUser socialUser,
DeviceType? deviceType = null,
string deviceRegistrationID = null)
{
using (var usersRepo = new UsersRepository())
{
var user = usersRepo.GetUserBySocialID(socialUser.SocialUserID, socialUser.SocialNetwork);
user = (user ?? new User()).CopyFrom(socialUser);
user.AuthToken = System.Guid.NewGuid().ToString();
if (user.ID == default(int))
{
usersRepo.Add(user);
}
usersRepo.SaveChanges();
return new TokenIdentity
{
AuthToken = user.AuthToken,
SocialUser = user,
UserID = user.ID
};
}
}
}
Global Action Filter:
public class TokenAuthenticationAttribute : System.Web.Http.Filters.ActionFilterAttribute
{
public override void OnActionExecuting(System.Web.Http.Controllers.HttpActionContext actionContext)
{
if (actionContext.Request.RequestUri.AbsolutePath.Contains("api/auth"))
{
return;
}
var authManager = new AuthCacheManager();
var user = authManager.CurrentUser;
if (user == null)
{
throw new HttpResponseException(HttpStatusCode.Unauthorized);
}
//Updates the authentication
authManager.Authenticate(user.SocialUser);
}
}
Global.asax registration:
GlobalConfiguration.Configuration.Filters.Add(new AuthFilterAttribute());
The idea is that AuthCacheManager extends AuthManager and decorates it's methods and properties. If there is nothing inside cache then go check database.
You could use token based authentication. Here's a great article illustrating how you could write a custom action filter that uses RSA public/private cryptography.