How do I authenticate a WCF Data Service? - wcf

I've created an ADO.Net WCF Data Service hosted in a Azure worker role. I want to pass credentials from a simple console client to the service then validate them using a QueryInterceptor. Unfortunately, the credentials don't seem to be making it over the wire.
The following is a simplified version of the code I'm using, starting with the DataService on the server:
using System;
using System.Data.Services;
using System.Linq.Expressions;
using System.ServiceModel;
using System.Web;
namespace Oslo.Worker
{
[ServiceBehavior(AddressFilterMode = AddressFilterMode.Any)]
public class AdminService : DataService<OsloEntities>
{
public static void InitializeService(
IDataServiceConfiguration config)
{
config.SetEntitySetAccessRule("*", EntitySetRights.All);
config.SetServiceOperationAccessRule("*", ServiceOperationRights.All);
}
[QueryInterceptor("Pairs")]
public Expression<Func<Pair, bool>> OnQueryPairs()
{
// This doesn't work!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
if (HttpContext.Current.User.Identity.Name != "ADMIN")
throw new Exception("Ooops!");
return p => true;
}
}
}
Here's the AdminService I'm using to instantiate the AdminService in my Azure worker role:
using System;
using System.Data.Services;
namespace Oslo.Worker
{
public class AdminHost : DataServiceHost
{
public AdminHost(Uri baseAddress)
: base(typeof(AdminService), new Uri[] { baseAddress })
{
}
}
}
And finally, here's the client code.
using System;
using System.Data.Services.Client;
using System.Net;
using Oslo.Shared;
namespace Oslo.ClientTest
{
public class AdminContext : DataServiceContext
{
public AdminContext(Uri serviceRoot, string userName,
string password) : base(serviceRoot)
{
Credentials = new NetworkCredential(userName, password);
}
public DataServiceQuery<Order> Orders
{
get
{
return base.CreateQuery<Pair>("Orders");
}
}
}
}
I should mention that the code works great with the signal exception that the credentials are not being passed over the wire.
Any help in this regard would be greatly appreciated!
Thanks....

You must throw an exception of type DataServiceException.

Related

Serve both REST and GraphQL APIs from .NET Core application

I have a .NET Core REST API server that is already serving customers.
Can I configure the API server to also support GraphQL by adding the HotChocolate library and defining the queries? Is it OK to serve both GraphQL and REST APIs from my .NET Core server?
Yes, supporting both REST APIs (controllers) and GraphQL is totally OK.
If you take libraries out of the picture, handling a GraphQL request just means handling an incoming POST to /graphql.
You can write a typical ASP.NET Core controller that handles those POSTs, if you want. Frameworks like Hot Chocolate provide middleware like .UseGraphQl() that make it more convenient to configure, but conceptually you can think of .UseGraphQl() as adding a controller that just handles the /graphql route. All of your other controllers will continue to work just fine!
There is a way you can automate having both APIs up and running at the same time using hotchocolate and schema stitching.
Basically I followed this tutorial offered by Ian webbington.
https://ian.bebbs.co.uk/posts/LessReSTMoreHotChocolate
Ian uses swagger schema from its API to create a graphql schema which saves us time if we think about it. It's easy to implement, however you still need to code to expose graphql endpoints.
This is what I implemented to connect all my graphql and rest APIs in just one API gateway. I'm sharing my custom implementation to have swagger schema (REST) running under hotchocolate (Graphql):
using System;
using HotChocolate;
using HotChocolate.AspNetCore;
using HotChocolate.AspNetCore.Playground;
using HotChocolate.AspNetCore.Voyager;
using HotChocolate.AspNetCore.Subscriptions;
using HotChocolate.Stitching;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using SmartGateway.Api.Filters;
using SmartGateway.Api.RestServices.SmartConfig;
namespace SmartGateway.Api.Extensions
{
public static class GraphQlExtensions
{
public static IServiceCollection AddGraphQlApi(this IServiceCollection services)
{
services.AddHttpClient("smartauth", (sp, client) =>
{
sp.SetUpContext(client); //GRAPHQL API
client.BaseAddress = new Uri(AppSettings.SmartServices.SmartAuth.Endpoint);
});
services.AddHttpClient("smartlog", (sp, client) =>
{
sp.SetUpContext(client); //GRAPHQL API
client.BaseAddress = new Uri(AppSettings.SmartServices.SmartLog.Endpoint);
});
services.AddHttpClient("smartway", (sp, client) =>
{
sp.SetUpContext(client); //GRAPHQL API
client.BaseAddress = new Uri(AppSettings.SmartServices.SmartWay.Endpoint);
});
services.AddHttpClient<ISmartConfigSession, SmartConfigSession>((sp, client) =>
{
sp.SetUpContext(client); //REST API
client.BaseAddress = new Uri(AppSettings.SmartServices.SmartConfig.Endpoint);
}
);
services.AddDataLoaderRegistry();
services.AddGraphQLSubscriptions();
services.AddStitchedSchema(builder => builder
.AddSchemaFromHttp("smartauth")
.AddSchemaFromHttp("smartlog")
.AddSchemaFromHttp("smartway")
.AddSchema(new NameString("smartconfig"), SmartConfigSchema.Build())
.AddSchemaConfiguration(c =>
{
c.RegisterExtendedScalarTypes();
}));
services.AddErrorFilter<GraphQLErrorFilter>();
return services;
}
public static IApplicationBuilder UseGraphQlApi(this IApplicationBuilder app)
{
app.UseWebSockets();
app.UseGraphQL("/graphql");
app.UsePlayground(new PlaygroundOptions
{
Path = "/ui/playground",
QueryPath = "/graphql"
});
app.UseVoyager(new PathString("/graphql"), new PathString("/ui/voyager"));
return app;
}
}
}
Set up HttpContext extension:
using System;
using System.Net.Http;
using System.Net.Http.Headers;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
namespace SmartGateway.Api.Extensions
{
public static class HttpContextExtensions
{
public static void SetUpContext(this IServiceProvider servicesProvider, HttpClient httpClient)
{
HttpContext context = servicesProvider.GetRequiredService<IHttpContextAccessor>().HttpContext;
if (context?.Request?.Headers?.ContainsKey("Authorization") ?? false)
{
httpClient.DefaultRequestHeaders.Authorization =
AuthenticationHeaderValue.Parse(context.Request.Headers["Authorization"].ToString());
}
}
}
}
You need this to handle and pass the HTTPClient to your swagger Sdk.
using System.Net.Http;
namespace SmartGateway.Api.RestServices.SmartConfig
{
public interface ISmartConfigSession
{
HttpClient GetHttpClient();
}
public class SmartConfigSession : ISmartConfigSession
{
private readonly HttpClient _httpClient;
public SmartConfigSession(HttpClient httpClient)
{
_httpClient = httpClient;
}
public HttpClient GetHttpClient()
{
return _httpClient;
}
}
}
This is my graphql Schema:
namespace SmartGateway.Api.RestServices.SmartConfig
{
public static class SmartConfigSchema
{
public static ISchema Build()
{
return SchemaBuilder.New()
.AddQueryType<SmartConfigQueries>()
.AddMutationType<SmartConfigMutations>()
.ModifyOptions(o => o.RemoveUnreachableTypes = true)
.Create();
}
}
public class SmartConfigMutations
{
private readonly ISmartConfigClient _client;
public SmartConfigMutations(ISmartConfigSession session)
{
_client = new SmartConfigClient(AppSettings.SmartServices.SmartConfig.Endpoint, session.GetHttpClient());
}
public UserConfigMutations UserConfig => new UserConfigMutations(_client);
}
}
Finally, this is how you publish endpoints:
using System.Threading.Tasks;
using SmartConfig.Sdk;
namespace SmartGateway.Api.RestServices.SmartConfig.UserConfigOps
{
public class UserConfigMutations
{
private readonly ISmartConfigClient _client;
public UserConfigMutations(ISmartConfigClient session)
{
_client = session;
}
public async Task<UserConfig> CreateUserConfig(CreateUserConfigCommand createUserConfigInput)
{
var result = await _client.CreateUserConfigAsync(createUserConfigInput);
return result.Response;
}
public async Task<UserConfig> UpdateUserConfig(UpdateUserConfigCommand updateUserConfigInput)
{
var result = await _client.UpdateUserConfigAsync(updateUserConfigInput);
return result.Response;
}
}
}
More documentation about hotchocolate and schema stitching here:
https://hotchocolate.io/docs/stitching

GetOwinContext through WCF throw nullreferenceException

i already have an MVC ASP.NET application where I manage authentication, using ASP.NET Identity for that.
I created a WCF service in the App, to allow other applications create new accounts using the service that my app provide to them.
When i call the WCF service, i get a NullReference from GetOwinContext() when service try to use userManager property.
This is my WCF Service Implementation:
using Microsoft.AspNet.Identity;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Web;
using MyCompany.MyProject.Core.Security.Auth;
using MyCompany.MyProject.Core.Security.Auth.Models;
using MyCompany.MyProject.MvcWebHost.Services.Contracts;
using Microsoft.AspNet.Identity.Owin;
using Microsoft.Owin.Host.SystemWeb;
using System.ServiceModel;
public class AuthenticationService : IAuthenticationService
{
private ApplicationUserManager _userManager;
public ApplicationUserManager UserManager
{
get
{
return _userManager ?? HttpContext.Current.GetOwinContext().GetUserManager<ApplicationUserManager>();
}
private set
{
_userManager = value;
}
}
public OperationResultDTO CreateUserAccount(UserAccountDTO userDto)
{
OperationResultDTO result = new OperationResultDTO();
var user = new ApplicationUser();
user.UserName = userDto.Identifier.ToString();
user.Email = userDto.Email;
Task<IdentityResult> adminresult = UserManager.CreateAsync(user, userDto.Password);
if (adminresult.IsCompleted && adminresult.IsFaulted != false)
{
result.IsSuccess = true;
result.HasError = false;
}
else
{
result.IsSuccess = false;
result.HasError = true;
result.ErrorMessage = "This is an error message!";
}
return result;
}
}
How can i solve it?
OWIN is not supported with WCF as you can see here, http://owin.org/#projects
If you still want to use OWIN you have to switch to REST or drop OWIN if you want to use WCF

Self-host (No IIS or WAS) WCF with a service that requires parameters

Hopefully this is an easy one. I'm wondering if this is possible - perhaps it is not. I'm attempting to self-host a WCF service (in my example below it is a console application). The service does not have a default constructor. It only contains a single parameter signature constructor. I need the service to be able to handle user sessions. Currently I am using Ninject DI. Here is a simple code solution I came up with to demonstrate my issue:
using System;
using System.ServiceModel;
using System.ServiceModel.Web;
using Ninject.Modules;
namespace ConsoleApplication1
{
public class Program
{
static void Main()
{
using (var webServiceHost = new WebServiceHost(typeof(MyWcf)))
{
var webHttpBinding = new WebHttpBinding();
var uri = new Uri("http://localhost:8000/");
webServiceHost.AddServiceEndpoint(typeof(IMyWcf), webHttpBinding, uri);
webServiceHost.Open();
Console.WriteLine("Service is ready...");
Console.ReadKey();
}
}
}
[ServiceContract]
public interface IMyWcf
{
[OperationContract, WebGet(UriTemplate = "")]
string HelloWorld();
}
[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerSession)]
public class MyWcf : IMyWcf
{
private readonly IMessage _customMessage = new Message("Default Message.");
public MyWcf(IMessage message)
{
_customMessage = message;
}
public string HelloWorld()
{
return _customMessage.Text;
}
}
public interface IMessage
{
string Text { get; }
}
public class Message : IMessage
{
public Message (string message)
{
Text = message;
}
public string Text { get; set; }
}
public class NinjectSetup : NinjectModule
{
public override void Load()
{
Bind<IMessage>().To<Message>()
.WithConstructorArgument("message", "Injected String Message.");
}
}
}
Obviously commenting out the parameterized constructor allows the service to run. But that does me no good. I don't want to use ServiceHostFactory because that apparently requires me to have a .svc/IIS. Is there a way around this? Can I just create a new MyWebServiceHost that inherits from WebServiceHost and override some method that will create a instance for the service?
Using Ruben's suggestion (in the comments) above, I was able to locate a working example within the Ninject.Extensions.Wcf source repository.

Using a Callback to pass an Event to a WCF Client

I am trying to have my WCF client receive info from a callback. I have created a Client Library that any WCF Client can use to connect to my WCF Service. I am uncertain if I should implement the Callback in the Client Library or the WCF Client itself.
I have attempted to create an event that will be fired by calling the OnNotification(...) method from within the callback. However, it cannot be called from within the Callback method and I'm not sure why.
Here is my Client Library used to connect to the WCF Service:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ServiceModel; //needed for WCF communication
namespace DCC_Client
{
public class DCCClient
{
private DuplexChannelFactory<ServiceReference1.IDCCService> dualFactory;
public ServiceReference1.IDCCService Proxy;
public DCCClient()
{
//Setup the duplex channel to the service...
NetNamedPipeBinding binding = new NetNamedPipeBinding();
dualFactory = new DuplexChannelFactory<ServiceReference1.IDCCService>(new Callbacks(), binding, new EndpointAddress("net.pipe://localhost/DCCService"));
}
public void Open()
{
Proxy = dualFactory.CreateChannel();
}
public void Close()
{
dualFactory.Close();
}
/// <summary>
/// Event fired an event is recieved from the DCC Service
/// </summary>
/// <param name="e"></param>
protected virtual void OnNotification(EventArgs e)
{
if (Notification != null)
{
Notification(this, e);
}
}
}
public class Callbacks : ServiceReference1.IDCCServiceCallback
{
void ServiceReference1.IDCCServiceCallback.OnCallback(string id, string message, Guid key)
{
//Can't call OnNotification(...) here?
}
}
}
OnNotification(...) cannot be called in the Callback method.
Here is an example of my how my WCF Client would be implemented using an EventHandler:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using DCC_Client;
namespace Client_Console_Test
{
class Program
{
private static DCCClient DCCClient;
static void Main(string[] args)
{
try
{
DCCClient = new DCCClient();
DCCClient.Notification += new EventHandler(DCCClient_Notification);
DCCClient.Open();
DCCClient.Proxy.DCCInitialize();
Console.ReadLine();
DCCClient.Proxy.DCCUninitialize();
DCCClient.Close();
}
catch (Exception e)
{
DCCClient.Log.Error(e.Message);
}
}
static void DCCClient_Notification(object sender, EventArgs e)
{
//Do something with this event
}
}
}
Is this the correct way to pass the callback info to my WCF Client? I feel like adding an EventHandler is redundant and I should just use the callback itself. Am I correct to have implemented the Callback in my Client Library, or should this be done in each WCF Client?
Thank you in advance.
I think I figured it out. I simply need to pass the DCCClient reference to the callback, and then call OnNotification() from it.
In DCC_Client:
public class DCCClient
{
private DuplexChannelFactory<ServiceReference1.IDCCService> dualFactory;
private Callbacks notificationCallback; //Add callback object here
public ServiceReference1.IDCCService Proxy;
public DCCClient()
{
//Setup the duplex channel to the service...
NetNamedPipeBinding binding = new NetNamedPipeBinding();
notificationCallback = new Callbacks(this); //Pass DCCClient reference here
dualFactory = new DuplexChannelFactory<ServiceReference1.IDCCService>(notificationCallback, binding, new EndpointAddress("net.pipe://localhost/DCCService"));
}
//....
public class Callbacks : ServiceReference1.IDCCServiceCallback
{
private DCCClient client;
public Callbacks(DCCClient client)
{
this.client = client; //grab client refernce
}
void ServiceReference1.IDCCServiceCallback.OnCallback(string id, string message, Guid key)
{
client.OnNotification(n); //send the event here
}
}

SilverLight Enabled Wcf Service - can't keep track of session

I'm new to Silverlight and WCF services. I'm trying to write a client application that can manipulate an object server side.
My problem is that each time my Silverlight client makes a call to the service, it enters into the constructor systematically
public SilverLightEnabledWcfService()
{
}
In the below example, I simply want to increment or decrement a number depending on the activity client side.
How am I supposed to do this properly?
I also tried to create a regular ASP.net client page and I got the same result, ie the server doesn't remember the session. So I don't think the problem is in my client, but I'm still happy to post the code if it helps.
Thanks !!
using System;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.ServiceModel.Activation;
using Count.Library;
namespace Count.WebApp
{
[ServiceContract(Namespace = "")]
[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerSession)]
[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Required)]
public class SilverLightEnabledWcfService
{
public SilverLightEnabledWcfService()
{
}
private Class1 _class1;
[OperationContract]
public int Add1()
{
if (_class1 == null)
_class1 = new Class1(0);
_class1.Add1();
return Value;
}
[OperationContract]
public int Remove1()
{
if (_class1 == null)
_class1 = new Class1(0);
_class1.Remove1();
return Value;
}
public int Value
{
get
{
return _class1.Count;
}
}
}
}
Sessions require the wsHttpBinding, but this is not supported by Silverlight. There are workarounds, though:
http://web-snippets.blogspot.com/2008_08_01_archive.html
http://forums.silverlight.net/forums/t/14130.aspx