I'm trying to assemble a .Net 6 WCF Service with WCFCore, using a basicHttpBinding, and I'm strugling to add a service authorization manager.
My purpose is to enable WCF to read and validate bearer tokens and use OAuth. I can't move to REST because of legacy applications compatibility, so I need to keep WCF but use bearer tokens.
My service at this stage is quite simple:
[ServiceContract]
public interface IService
{
[OperationContract]
string GetData(int value);
[OperationContract]
CompositeType GetDataUsingDataContract(CompositeType composite);
}
public class Service : IService
{
public string GetData(int value)
{
return string.Format("You entered: {0}", value);
}
public CompositeType GetDataUsingDataContract(CompositeType composite)
{
if (composite == null)
{
throw new ArgumentNullException("composite");
}
if (composite.BoolValue)
{
composite.StringValue += "Suffix";
}
return composite;
}
}
// Use a data contract as illustrated in the sample below to add composite types to service operations.
[DataContract]
public class CompositeType
{
bool boolValue = true;
string stringValue = "Hello ";
[DataMember]
public bool BoolValue
{
get { return boolValue; }
set { boolValue = value; }
}
[DataMember]
public string StringValue
{
get { return stringValue; }
set { stringValue = value; }
}
}
My Program.cs:
var builder = WebApplication.CreateBuilder();
builder.Services.AddServiceModelServices();
builder.Services.AddServiceModelConfigurationManagerFile("wcf.config");
builder.Services.AddServiceModelMetadata();
builder.Services.AddSingleton<IServiceBehavior, UseRequestHeadersForMetadataAddressBehavior>();
builder.Services.AddSingleton<OAuthAuthorizationManager>();
var app = builder.Build();
app.UseServiceModel(bld =>
{
bld.AddServiceEndpoint<Service, IService>(new BasicHttpBinding(BasicHttpSecurityMode.Transport), "/Service.svc");
var mb = app.Services.GetRequiredService<ServiceMetadataBehavior>();
mb.HttpsGetEnabled = true;
});
app.Run();
Then my wcf.config:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<system.serviceModel>
<bindings>
<basicHttpBinding>
<binding name="basicBinding" receiveTimeout="00:10:00">
<security mode="Transport" />
</binding>
</basicHttpBinding>
</bindings>
<services>
<service name="CoreWCFService.Service" behaviorConfiguration="Default">
<endpoint address="basic" binding="basicHttpBinding" bindingConfiguration="basicBinding" contract="CoreWCFService.IService" />
</service>
</services>
<behaviors>
<serviceBehaviors>
<behavior name="Default">
<serviceDebug includeExceptionDetailInFaults="true" />
<serviceMetadata httpGetEnabled="true" />
<serviceAuthorization serviceAuthorizationManagerType="CoreWCFService.OAuthAuthorizationManager,CoreWCFService" />
<dataContractSerializer maxItemsInObjectGraph="10000000" />
</behavior>
</serviceBehaviors>
</behaviors>
</system.serviceModel>
</configuration>
But when I call the service with tokens, nothing happens on the authorization manager, the operation runs simply ignoring this service behavior.
Is there anyone out there that can help me with this?
You may refer to the Corewcf project template. There are a few things to note:
The interface and its implementation need to be separated to facilitate subsequent maintenance and invocation of the interface.
We need to look at the UseServiceModel part in Program.cs.
Related
var dataToSend = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(mi));
var req = HttpWebRequest.Create("http://localhost/Service1.svc/json/MethodName");
req.ContentType = "application/json";
req.ContentLength = dataToSend.Length;
req.Method = "POST";
req.GetRequestStream().Write(dataToSend, 0, dataToSend.Length);
var response = req.GetResponse();
Here "/json" is my endpoint address and my service is configured with multiple endpoints. As per image here, request i sent is recieving null at server.
If my request format is not proper then suggest proper way to call this service.
// Service inter face
[ServiceContract]
public interface IService
{
[OperationContract]
[WebInvoke(Method="POST")]
Response MethodName(Request request);
}
// Service1
public class Service1 : IService
{
public Response MethodName(Request request)
{
some logical operation....
}
}
// End point configuration (Web config)
<endpoint address="json" behaviorConfiguration="jsonBehavior"
binding="webHttpBinding" bindingConfiguration="webHttpBindingJson"
name="jsonn" contract="Service1.IService" />
<endpoint address="xml" behaviorConfiguration="poxBehavior" binding="webHttpBinding"
bindingConfiguration="webHttpBindingXml" name="xmll" contract="Service1.IService" />
<endpointBehaviors>
<behavior name="jsonBehavior">
<enableWebScript />
</behavior>
<behavior name="poxBehavior">
<enableWebScript />
</behavior>
</endpointBehaviors>
<webHttpBinding>
<binding name="webHttpBindingJson">
<security mode="None" />
</binding>
<binding name="webHttpBindingXml">
<security mode="None" />
</binding>
</webHttpBinding>
// Request class
[DataContract]
public class Request
{
string userMobile;
string otp;
[DataMember]
public string UserMobile
{
get { return userMobile; }
set { userMobile = value; }
}
[DataMember]
public string OTP
{
get { return otp; }
set { otp = value; }
}
}
Finally i found for this.
I modified endpoint of json behaviour configuration to this,
<behavior name="jsonBehavior">
<webHttp defaultBodyStyle ="Bare"/>
<!--<enableWebScript />-->
</behavior>
and removed enableWebScript. Finally my code working.
For my recent project, I created a Web service that returns an array of custom type to jquery client-side code. WCF is called by $.ajax command and is in the same domain.
When I run my web applicaiton on localhost (which is IIS run on local machine), everything works fine. When I deploy it to our integration server, suddenly ajax call to WCF ends with an error: "parsererror - unterminated string constant" and status of 200. Returned message is however something like "[{\"Text\":\"Test dodatnih naslov", which of course is not a correct json format.
Correct response should have been: "[{"Text":"Test dodatnih naslovov","Value":"100"},{"Text":"Test dodatnih naslovov - ISO2","Value":"101"},{"Text":"UPDATE","Value":"102"}]"
I have traced WCf service for malfuncitons, but it does not seem to be crashing. I also tried and set timeout to ajax call, but to no avail. Some help would be much appreciated.
My IIS is IIS7, where integration runs IIS6 on Windows Server 2008.
js file
function InsuranceClientContact_ItemsRequesting(o, e) {
var $ = $telerik.$;
var urlSvc = ServiceBaseUrl + '/GetContacts'
$.ajax({
type: "POST",
url: urlSvc,
data: '{"ixClient": ' + selectedItemId + '}', //selectedItemId is a positive number
dataType: "json",
contentType: "application/json; charset=utf-8",
success: function (data) {
// do something
},
error: function (result) {
var msg = result.status + " - " + result.statusText;
setTimeout(function () { throw new Error(msg) }, 0);
}
});
wcf interface
namespace Sid.Skode.Web.Services.Populate {
[ServiceContract]
public interface IInsuranceClientContactService {
[OperationContract]
[WebInvoke(Method="POST",
BodyStyle=WebMessageBodyStyle.WrappedRequest,
ResponseFormat=WebMessageFormat.Json)]
Contact[] GetContacts(long ixClient);
}
[DataContract]
public class Contact {
[DataMember]
public string Text;
[DataMember]
public string Value;
}
}
wcf service implementation
namespace Sid.Skode.Web.Services.Populate {
[AspNetCompatibilityRequirements( RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed )]
public class InsuranceClientContactService : IInsuranceClientContactService {
public Contact[] GetContacts( long ixClient ) {
return GetContactsFromDatabase( ixClient );
}
#region Private methods
private Contact[] GetContactsFromDatabase( long ixClient ) {
DataTable dt = GetDataFromDataBaseById( ixClient );
return ConvertDataTableToContactArray( dt );
}
private DataTable GetDataFromDataBaseById( long ixClient ) {
AutoCompleteBLL model = new AutoCompleteBLL();
return model.SearchContactsByPartner( ixClient );
}
private Contact[] ConvertDataTableToContactArray( DataTable dt ) {
Contact[] rgContact = new Contact[dt.Rows.Count];
int cnContact = 0;
foreach (DataRow dr in dt.Rows) {
if (!dr.IsNull( "NAZIV" )) {
Contact contact = new Contact();
contact.Text = dr["NAZIV"].ToString();
contact.Value = dr["ID_DODATEN_KONTAKT"].ToString();
rgContact[cnContact++] = contact;
}
}
return rgContact;
}
#endregion
}
}
web.config wcf part
<system.serviceModel>
<behaviors>
<serviceBehaviors>
<behavior name="httpServiceBehavior">
<serviceMetadata httpsGetEnabled="true" />
<serviceDebug includeExceptionDetailInFaults="true" />
</behavior>
</serviceBehaviors>
<endpointBehaviors>
<behavior name="httpEndpointBehavior">
<webHttp/>
</behavior>
</endpointBehaviors>
</behaviors>
<bindings>
<webHttpBinding>
<binding name="webHttpBindingWithTransportWindowsSecurity">
<security mode="Transport">
<transport clientCredentialType="Windows" />
</security>
</binding>
</webHttpBinding>
</bindings>
<serviceHostingEnvironment multipleSiteBindingsEnabled="false" aspNetCompatibilityEnabled="true" />
<services>
<service name="Sid.Skode.Web.Services.Populate.InsuranceClientContactService" behaviorConfiguration="httpServiceBehavior">
<endpoint address="" binding="webHttpBinding" bindingConfiguration="webHttpBindingWithTransportWindowsSecurity"
contract="Sid.Skode.Web.Services.Populate.IInsuranceClientContactService"
behaviorConfiguration="httpEndpointBehavior">
</endpoint>
<endpoint
address="mex"
binding="mexHttpsBinding"
bindingConfiguration=""
contract="IMetadataExchange"/>
</service>
</services>
</system.serviceModel>
As described here, you need remove all instances of RadCompression http module from web config. Then, it works.
I have a service with below code
TaskService.svc
[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
public class TodoService : ITodoService
{
private readonly List<Todo> _dataSource;
public TodoService()
{
_dataSource = new List<Todo>();
}
[WebGet(UriTemplate = "/Todos")]
public List<Todo> Todos()
{
return _dataSource;
}
}
and the data transfer object is as below
[ServiceContract]
public interface ITodoService
{
[OperationContract]
List<Todo> Todos();
}
[DataContract]
public class Todo
{
[DataMember]
public string Name { get; set; }
[DataMember]
public bool Completed { get; set; }
}
the web.config as below
<services>
<service name="SampleService.TodosBehavior">
<endpoint address="" behaviorConfiguration="SampleService.TodosBehavior"
binding="webHttpBinding" contract="SampleService.TodoService" />
</service>
</services>
<behaviors>
<endpointBehaviors>
<behavior name="SampleService.TodosBehavior">
<enableWebScript />
</behavior>
</endpointBehaviors>
<serviceBehaviors>
<behavior name="">
<serviceMetadata httpGetEnabled="true" />
<serviceDebug includeExceptionDetailInFaults="false" />
</behavior>
</serviceBehaviors>
</behaviors>
the application is hosted in IIS 7 with virtual directory SampleService. When ever i invoke using browser http://localhost/SampleService/TaskService.svc/Todos i am greeted with nothing. But i expected a empty array. Why is this so?
I think the WebGet() needs to be in your interface (ServiceContract) rather than your implementation of that interface. Also, do you need the slash before "Todos" in your UriTemplate?
[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
public class TodoService : ITodoService
{
private readonly List<Todo> _dataSource;
public TodoService()
{
_dataSource = new List<Todo>();
}
public List<Todo> Todos()
{
return _dataSource;
}
}
[ServiceContract]
public interface ITodoService
{
[WebGet(UriTemplate = "Todos")]
[OperationContract]
List<Todo> Todos();
}
If you're hosting in IIS, what does your TaskService.svc file look like?
EDIT: Oh, this IS your .svc file. Never mind then.
I just need to secure my WF services. Can't find any resources on this. How to do it?
Already tried:
class Program
{
static void Main(string[] args)
{
using (WorkflowServiceHost host = new WorkflowServiceHost(new Workflow1(), new Uri("http://localhost/Test")))
{
host.Credentials.UserNameAuthentication.UserNamePasswordValidationMode = System.ServiceModel.Security.UserNamePasswordValidationMode.Custom;
host.Credentials.UserNameAuthentication.CustomUserNamePasswordValidator = new Test();
host.Open();
Console.Write("ready");
Console.ReadLine();
}
}
}
public class Test : UserNamePasswordValidator
{
public Test()
{
Console.Write("hit");
}
public override void Validate(string userName, string password)
{
Console.Write("never hit");
}
}
And a config
<bindings>
<wsHttpBinding>
<binding>
<security mode="Message">
<message clientCredentialType="UserName" />
</security>
</binding>
</wsHttpBinding>
</bindings>
<serviceBehaviors>
<behavior>
<serviceMetadata httpGetEnabled="true"/>
<serviceDebug includeExceptionDetailInFaults="false"/>
<!--<serviceCredentials>
<userNameAuthentication userNamePasswordValidationMode="Custom" customUserNamePasswordValidatorType="myAssembly.Test, myAssembly" />
</serviceCredentials>-->
</behavior>
</serviceBehaviors>
Can't create a fixed name endpoint because they are dynamically created
UPDATE - I tried the configuration bellow and worked, but I want a more granular way to set what binding each service use
<protocolMapping>
<add scheme="http" binding="wsHttpBinding"/>
</protocolMapping>
We have an episode of Workflow TV that should help. Workflow TV - Workflow Services Security
As far as the messaging part this is just WCF so anything you can do with WCF should work here.
That said with workflow you typically need more fine grained control on all but the first request. For example all employees can start en expense report but only the employee who started a specific expense report can add expenses to it and submit it. You can do these kind of security checks using the WF Security Pack.
A little hackish, but works. Overrided WorkflowServiceHost in order to grab unknown contract names and added service endpoints for each one.
const string DEFAULT_WORKFLOW_SERVICE_BINDING_NAME = "WorkflowDefaultBinding";
static void Main(string[] args)
{
MyWorkflowServiceHost host = new MyWorkflowServiceHost(new CountingWorkflow2(), new Uri(hostBaseAddress));
foreach (var contractName in host.ImplementedContractsNames)
{
// now I'm able to choose which binding to use depending on a condition
var binding = new WSHttpBinding(DEFAULT_WORKFLOW_SERVICE_BINDING_NAME);
host.AddServiceEndpoint(contractName, binding, string.Empty);
}
}
And MyWorkflowServiceHost
public class MyWorkflowServiceHost : WorkflowServiceHost
{
public MyWorkflowServiceHost(Activity activity, params Uri[] baseAddresses)
: base(activity, baseAddresses)
{
}
private IDictionary<string, System.ServiceModel.Description.ContractDescription> _implementedContracts;
public IEnumerable<string> ImplementedContractsNames
{
get
{
foreach (var contract in _implementedContracts)
yield return contract.Key;
}
}
protected override System.ServiceModel.Description.ServiceDescription CreateDescription(out System.Collections.Generic.IDictionary<string, System.ServiceModel.Description.ContractDescription> implementedContracts)
{
System.ServiceModel.Description.ServiceDescription description = base.CreateDescription(out implementedContracts);
_implementedContracts = implementedContracts;
return description;
}
}
Adding a unamed WSHttpBinding and the following section on service model should work too, but for default configuration
<protocolMapping>
<add scheme="http" binding="wsHttpBinding"/>
</protocolMapping>
I'm writing a WCF (net.tcp) file transfer service which will eventually split files into several pieces and transfer said pieces to the client from the server/service. Currently the client and server are console apps.
While writing this service, I have at various times gotten the following exception;
System.ServiceModel.CommunicationException: The socket connection was aborted. This could be caused by an error processing your message or a receive timeout being exceeded by the remote host, or an underlying network resource issue. Local socket timeout was '01:59:59.4220000'.
Actually, it starts as a Unhandled Exception: System.Reflection.TargetInvocationException: An exception occurred during the operation, making the result invalid. Check InnerException for exception details. ---> then the CommunicationException text above is right here.
In typical Microsoft fashion, that exception message is beyond unhelpful, so I'm finally calling out to the community to see if I can tackle this issue.
Also, in case this is important, the client is calling two of the service's methods Asynchronously ( InitGetFilePart() and GetFilePart() ). According to my logs, the first call to InitGetFilePartAsync(1, 1) gets handled all the way to the end; meaning that it's 'Completed' handler is called, which in turn calls vcClient.GetFilePartAsync(FileXferCargo, 1), and it's handler then spawns a BackgroundWorker thread (workers[chunkNum].
RunWorkerAsync(cargoHolder[chunkNum] where chunkNum=1) which itself completes. This is right about the time I get the TargetInvocationException mentioned above.
In the past I have made various tweaks (sadly, I can't remember what exactly) to the App.config to make this exception go away, but now nothing I do seems to make any difference and I just do NOT understand why this keeps happening.
I have read other advice on this matter, including "You have to catch the exception on the client, Abort current proxy and create and open new one." Well, I tried that but it doesn't appear I am able to catch this exception.
I also read that it could be due to sending too much data over the wire, but when I try to send my little 4k test file, I get the same exception. During my investigations, I also read that I can call 1 WCF service function/method, many times using the *Async() calls with the UserState parameter, which I am doing.
I will admit to being a relitive n00b when it comes to WCF, but I'm pretty sure I have my config files set correctly for what I'm trying to do.
Here are the client and server app.config files, the service interface definition and the top of the service's implementation class.
Client's App.config:
<system.serviceModel>
<bindings>
<netTcpBinding>
<binding name="MyTcpBinding_IFileXferService"
receiveTimeout="02:00:00"
sendTimeout="02:00:00"
transferMode="Streamed"
maxBufferSize="65536"
maxReceivedMessageSize="2147483647">
<readerQuotas maxStringContentLength="2147483647"
maxArrayLength="2147483647"
maxBytesPerRead="65536" />
<security mode="Transport">
<transport clientCredentialType="None">
<extendedProtectionPolicy policyEnforcement="Never" />
</transport>
</security>
</binding>
</netTcpBinding>
</bindings>
<behaviors>
<endpointBehaviors>
<behavior name="ClientConfigBehavior">
<dataContractSerializer maxItemsInObjectGraph="2147483647" />
<clientCredentials>
<serviceCertificate>
<authentication certificateValidationMode="None" />
</serviceCertificate>
</clientCredentials>
</behavior>
</endpointBehaviors>
</behaviors>
<client>
<endpoint name="ClientConfig"
behaviorConfiguration="ClientConfigBehavior"
binding="netTcpBinding"
bindingConfiguration="MyTcpBinding_IFileXferService"
contract="ServiceRefs.IFileXferService" />
</client>
</system.serviceModel>
Server's App.config:
<bindings>
<netTcpBinding>
<!-- Under <netTcpBinding> setting the listenBacklog,
maxConnections, and maxBuffer* values high -->
<binding name="MyTcpBinding_IFileXferService"
receiveTimeout="02:00:00"
sendTimeout="02:00:00"
openTimeout="00:01:00"
transferMode="Streamed"
portSharingEnabled="true"
listenBacklog="32"
maxConnections="64"
maxBufferSize="65536"
maxReceivedMessageSize="2147483646">
<security mode="Transport">
<transport clientCredentialType="None" />
</security>
</binding>
</netTcpBinding>
</bindings>
<services>
<service name="MediaServer.LNMediaServerSvc"
behaviorConfiguration="ServerConfigBehavior">
<host>
<baseAddresses>
<add baseAddress="net.tcp://lngsead148191a:9000/fileXferSvc"/>
</baseAddresses>
</host>
<endpoint name="mainEndPoint"
binding="netTcpBinding"
bindingConfiguration="MyTcpBinding_IFileXferService"
contract="ServiceInterfaces.IFileXferService" />
</service>
</services>
<behaviors>
<serviceBehaviors>
<behavior name="ServerConfigBehavior">
<serviceDebug includeExceptionDetailInFaults="true"/>
<serviceThrottling maxConcurrentCalls="64" />
<dataContractSerializer maxItemsInObjectGraph="2147483646" />
<serviceCredentials>
<serviceCertificate findValue="tp_value"
x509FindType="FindByThumbprint" />
</serviceCredentials>
</behavior>
</serviceBehaviors>
</behaviors>
</system.serviceModel>
The Service interface is defined as follows;
[DataContract(IsReference = true)]
public class FileData
{
private long m_startPos;
[DataMember]
public long StartPosition
{
get { return m_startPos; }
set { m_startPos = value; }
}
private long m_endPos;
[DataMember]
public long EndPosition
{
get { return m_endPos; }
set { m_endPos = value; }
}
private byte m_chunkNumber;
[DataMember]
public byte ChunkNumber
{
get { return m_chunkNumber; }
set { m_chunkNumber = value; }
}
private long m_chunkSize;
[DataMember]
public long ChunkSize
{
get { return m_chunkSize; }
set { m_chunkSize = value; }
}
private string md5Hash;
[DataMember]
public string MD5Hash
{
get { return md5Hash; }
set { md5Hash = value; }
}
private string m_destFileSpec;
[DataMember]
public string DestinationFileSpec
{
get { return m_destFileSpec; }
set { m_destFileSpec = value; }
}
private string m_srcFileSpec;
[DataMember]
public string SourceFileSpec
{
get { return m_srcFileSpec; }
set { m_srcFileSpec = value; }
}
private Stream m_sourceStream;
[DataMember]
public Stream SourceStream
{
get { return m_sourceStream; }
set { m_sourceStream = value; }
}
private UInt32 m_JobNo;
[DataMember]
public UInt32 JobNumber
{
get { return m_JobNo; }
set { m_JobNo = value; }
}
private UInt32 m_fileNumber;
[DataMember]
public UInt32 FileNumber
{
get { return m_fileNumber; }
set { m_fileNumber = value; }
}
private long m_fileSize;
[DataMember]
public long FileSize
{
get { return m_fileSize; }
set { m_fileSize = value; }
}
}
[DataContract]
public partial class FileXferCargo
{
private FileData m_fileData;
[DataMember]
public FileData FileData
{
get { return m_fileData; }
set { m_fileData = value; }
}
private bool m_cancelled;
[DataMember]
public bool Cancelled
{
get { return m_cancelled; }
set { m_cancelled = value; }
}
private long m_errorCode;
[DataMember]
public long ErrorCode
{
get { return m_errorCode; }
set { m_errorCode = value; }
}
private Exception m_exceptionObj;
[DataMember]
public Exception Exception
{
get { return m_exceptionObj; }
set { m_exceptionObj = value; }
}
}
[ServiceContract]
public interface IFileXferService
{
[OperationContract]
bool InitFileRequest(ref FileXferCargo fileRequest);
[OperationContract]
bool InitGetFilePart(ref FileXferCargo fileCargo);
[OperationContract]
Stream GetFilePart(FileXferCargo fileCargo);
[OperationContract]
int CloseFile(FileData fileData);
}
The Service implementation class is defined as follows;
[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single,
ConcurrencyMode = ConcurrencyMode.Multiple,
UseSynchronizationContext = false)
]
public class LNMediaServerSvc : IFileXferService
{
...
}
For your netTcpBinding, set the security mode to none, on both the client and the server configuration files:
<security mode="None" />
this is a quick fix.
If you need to have security enable for netTcpBinding then you have to implement delegation and impersonation.
read more here: http://msdn.microsoft.com/en-us/library/ms730088.aspx
From what I have read and googled this is an issue of multi-hop impersonation. Impersonation can carry over to one hop only. By setting the security mode to none you prevent the authentication step when calling the WCF service.
Don't have a comment button anymore so I'll put this here. Yes, I have turned on tracing, but it seems like I'm going to need to read a BOOK to understand all the stuff the trace data is trying to tell me. That said, I looked over the trace info and the one thing I couldn't see was anything leading up to the socket being aborted. Just seems to happen for no reason. :(
For me it was because of not having net.tcp binding in web.config in the server which hosted the WCF. When I added the same binding for the service I did not get the exception. So you need to have the binding in both sides in Web and WCF if they are hosted in different servers.