Let me start by saying that this is my first time working with WCF web services and I've been battling error for the last 3 days. These issues have been answered many times at Stackoverflow, however, I've tried most solutions and haven't been successful yet, so I need some help in figuring out the right way.
Now some background. I'm creating an ASP.Net MVC 5 project, I've to connect to WCF web services provided by Epicor (an ERP solution). My project, the ERP and its web services are all hosted on an internal IIS instance. The services are exposed using both BasicHTTP and NetTCP protocols. The application pool on which the web service and ERP are hosted uses identity.
One of the web service is called Company.svc and it is exposed as:
<wsdl:service name="CompanySvcFacade">
<wsdl:port name="BasicHttpBinding_CompanySvcContract" binding="tns:BasicHttpBinding_CompanySvcContract">
<soap:address location="http://pilotserver/ERP100700/Ice/BO/Company.svc"/>
</wsdl:port>
<wsdl:port name="CustomBinding_CompanySvcContract" binding="tns:CustomBinding_CompanySvcContract">
<soap12:address location="net.tcp://pilotserver/ERP100700/Ice/BO/Company.svc"/>
<wsa10:EndpointReference>
<wsa10:Address>net.tcp://pilotserver/ERP100700/Ice/BO/Company.svc</wsa10:Address>
<Identity xmlns="http://schemas.xmlsoap.org/ws/2006/02/addressingidentity">
<Upn>pilotserver\Administrator</Upn>
</Identity>
</wsa10:EndpointReference>
</wsdl:port>
</wsdl:service>
In my project, my web.config has the following:
<system.serviceModel>
<bindings>
<basicHttpBinding>
<binding name="BasicHttpBinding_CompanySvcContract" />
</basicHttpBinding>
<customBinding>
<binding name="CustomBinding_CompanySvcContract">
<security defaultAlgorithmSuite="Default" authenticationMode="UserNameOverTransport"
requireDerivedKeys="true" includeTimestamp="true" messageSecurityVersion="WSSecurity11WSTrustFebruary2005WSSecureConversationFebruary2005WSSecurityPolicy11BasicSecurityProfile10">
<localClientSettings detectReplays="false" />
<localServiceSettings detectReplays="false" />
</security>
<textMessageEncoding />
<windowsStreamSecurity />
<tcpTransport />
</binding>
</customBinding>
</bindings>
<client>
<endpoint address="http://pilotserver/ERP100700/Ice/BO/Company.svc"
binding="basicHttpBinding" bindingConfiguration="BasicHttpBinding_CompanySvcContract"
contract="CompanyService.CompanySvcContract" name="BasicHttpBinding_CompanySvcContract" />
<endpoint address="net.tcp://pilotserver/ERP100700/Ice/BO/Company.svc"
binding="customBinding" bindingConfiguration="CustomBinding_CompanySvcContract"
contract="CompanyService.CompanySvcContract" name="CustomBinding_CompanySvcContract">
<identity>
<userPrincipalName value="pilotserver\Administrator" />
</identity>
</endpoint>
</client>
And I'm trying to consume the web service in the client using the following code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.ServiceModel.Description;
using System.ServiceModel.Dispatcher;
using EpicorTestApp.CompanyService;
using NLog;
namespace EpicorTestApp.Controllers
{
public class HomeController : Controller
{
CompanySvcContractClient CompanyService = new CompanySvcContractClient("CustomBinding_CompanySvcContract");
//CompanySvcContractClient CompanyService = new CompanySvcContractClient("BasicHttpBinding_CompanySvcContract");
private Logger logger = LogManager.GetCurrentClassLogger();
public ActionResult Index()
{
ViewBag.Message = "Welcome to ASP.NET MVC!";
bool morePages = false;
CompanyService.ClientCredentials.UserName.UserName = "Administrator";
CompanyService.ClientCredentials.UserName.UserName = "myPassword";
CompanyListTableset companyList = CompanyService.GetList("", 0, 0, out morePages);
CompanyListTable companies = companyList.CompanyList;
foreach (CompanyListRow companyListRow in companies)
{
logger.Info("Company: " + companyListRow.Company);
}
return View();
}
}
}
For the client binding, I've tried both BasicHttp and NetTCP (as CustomBinding), both resulting in some errors. When I create a BasicHttp binding, I use the following service reference configuration:
and upon running this configuration, I receive an error for "Access is denied. Exception Details: System.ServiceModel.Security.SecurityAccessDeniedException: Access is denied."
And for nettcp binding, when I try to create a service reference, I receive an error with the message "The URI prefix is not recognized. Metadata contains a reference that cannot be resolved: net.tcp://localhost/ERP100700/Ice/BO/Company.svc'. I've tried using both localhost and pilotserver in the url.
I've tried running the application both in debug mode (ISS-Express) and publishing it to IIS, but same result. What am I doing wrong and how can I resolve this issue?
In the HomeController, it would seem that this
CompanyService.ClientCredentials.UserName.UserName = "Administrator";
CompanyService.ClientCredentials.UserName.UserName = "myPassword";
Should be something like this
CompanyService.ClientCredentials.UserName.UserName = "Administrator";
CompanyService.ClientCredentials.UserName.Password = "myPassword
You are passing the UserName twice in the code as you present it.
The reason why I was getting all these errors was that in Web.config for Epicor's web services (inside IIS), https scheme for BasicHTTP was disabled/ comment out. I had to add the following to make my application work.
<remove scheme="https" />
<add scheme="https" binding="basicHttpBinding" bindingConfiguration="BasicHttp"/>
This is the default behavior in Epicor.
Related
I had created a project with MVC 5 and WCF.I had a WCF Service with AspNetCompatibilityRequirementsMode.Allowed and also in web.config
<serviceHostingEnvironment multipleSiteBindingsEnabled="true" aspNetCompatibilityEnabled="true" />
In MVC Controller i am adding value to session
public class HomeController : Controller
{
ServiceReference1.MyService2Client ur = new ServiceReference1.MyService2Client();
public ActionResult Index()
{
HttpContext.Session.Add("UserId", 1);
ViewBag.msg = ur.Test();
return View();
}
}
In WCF Service :
[AspNetCompatibilityRequirements(RequirementsMode
= AspNetCompatibilityRequirementsMode.Allowed)]
public class MyService2 : IMyService2
{
private Test _test;
public MyService2(Test test)
{
_test = test;
}
public string Test()
{
var test = HttpContext.Current.Session["UserId"]; // Its Coming as NULL
return "Serivce Success";
}
}
Even though i am adding session variable in MVC Controller and try to access the session variable in WCF Service, the session variable is not present when request comes to WCF Service.
In MVC web.config:
<system.serviceModel>
<bindings>
<basicHttpBinding>
<binding name="BasicHttpBinding_IMyService2" />
</basicHttpBinding>
</bindings>
<client>
<endpoint address="http://localhost:30380/MyService2.svc" binding="basicHttpBinding"
bindingConfiguration="BasicHttpBinding_IMyService2" contract="ServiceReference1.IMyService2"
name="BasicHttpBinding_IMyService2" />
</client>
</system.serviceModel>
In WCF web.config:
<system.serviceModel>
<behaviors>
<serviceBehaviors>
<behavior>
<!-- To avoid disclosing metadata information, set the values below to false before deployment -->
<serviceMetadata httpGetEnabled="true" httpsGetEnabled="true" />
<!-- To receive exception details in faults for debugging purposes, set the value below to true. Set to false before deployment to avoid disclosing exception information -->
<serviceDebug includeExceptionDetailInFaults="false" />
</behavior>
</serviceBehaviors>
</behaviors>
<protocolMapping>
<add binding="basicHttpsBinding" scheme="https" />
</protocolMapping>
<serviceHostingEnvironment aspNetCompatibilityEnabled="true" />
</system.serviceModel>
Help me out to resolve it
There is no httpcontext in a wcf web service,
https://learn.microsoft.com/en-us/dotnet/framework/wcf/feature-details/wcf-services-and-aspnet
As the document mentioned, we use the OperationContext.
https://learn.microsoft.com/en-us/dotnet/api/system.servicemodel.operationcontext?redirectedfrom=MSDN&view=netframework-4.7.2
if you want to store the user data, you should enable the WCF session mode.
In that case, the wcf server has the ablilty to identity the client. It can be associated with all messages sent from specific clients to specific service instances.
https://social.msdn.microsoft.com/Forums/vstudio/en-US/27896125-b61e-42bd-a1b0-e6da5c23e6fc/httpcontextcurrent-in-wcf
We have a multi-tenanted MVC app, meaning that exactly the same app is published to multiple IIS virtual directories / applications, and then the app its self works out who it is, and skins its self (css) accordingly.
This is all very well, but anything logged by ELMAH in our elmah database gets logged under the same applicationName, as this is pulled out of Web.Config elmah section below where everything would be logged as "MyappName" :
<configuration>
[...]
<elmah>
<security allowRemoteAccess="false" />
<errorLog
type="Elmah.SqlErrorLog, Elmah"
connectionStringName="elmah"
applicationName="MyappName" />
</elmah>
</configuration>
The question is therefore how to override the applicationName setting from web.config with something specific so we can distinguish errors for a given tenant web site.
As this is configurable within the web.config, ELMAH are already providing you with a way to specify the application name when the application is deployed to different locations - it's just a case of making use of it.
This would generally be something that you would manipulate as part of your deployment steps. If you are doing it manually then it's going to be a pain, but it could be easily manipulated by using a web.config transform.
<?xml version="1.0"?>
<configuration xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform">
<elmah>
<errorLog applicationName="MyappName" xdt:Transform="SetAttributes" xdt:Locator="Match(name)" />
</elmah>
</configuration>
I wonder if the following might work, if you put the following into your Global.asax:
var service = ServiceCenter.Current;
ServiceCenter.Current = context =>
{
var connectionString = "YOUR CONNECTION STRING";
var container = new ServiceContainer(service(context));
var log = new SqlErrorLog(connectionString) { ApplicationName = "APP NAME HERE" };
container.AddService(typeof(ErrorLog), log);
return container;
};
I am new in the SimpleMembership model. WebSecurity works fine in the Web pages, but I have problems when I use it in the services.
I have some web services, working under:
<binding name="SecureBasicBindingWithMembershipConfig">
<security mode="TransportWithMessageCredential">
<transport clientCredentialType="None" />
<message clientCredentialType="UserName" />
</security>
</binding>
I try to recover the user Id from a web service.
If I use WebSecurity.IsAuthenticated, it returns IsAuthenticated = 'WebMatrix.WebData.WebSecurity.IsAuthenticated' threw an exception of type 'System.ArgumentNullException'
However, using System.Web.Security.Membership.GetUser() the user Id is correctly returned.
Can WebSecurity be used within a WCF service or I am doing something wrong?
I'm making an asp.net MVC application that communicates with wcf services on IIS.
The asp.net MVC application works with a normal login mechanism (username, password). In the wcf services I want to log in (with this password and username) to get the windowsidentity of the user.(the security on the wcf services are windows based)
The issue is how can I communicate from the asp.net MVC application with the services in this way and keep the asp.net MVC application as stateless as possible (putting a password in the session state is definately not allowed)
Ideas about how to get this done are very much appreciated.
This drawing might make things clearer:
Here's an article explaining step by step how to authenticate using an username/password in a WCF service:
http://blog.adnanmasood.com/2010/04/29/step-by-step-guide-for-authenticating-wcf-service-with-username-and-password-over-ssl/
It uses a custom UserNamePasswordValidator on the service side:
public class CustomValidator : UserNamePasswordValidator
{
public override void Validate(string userName, string password)
{
if (userName == "test" && password == "secret")
{
return;
}
throw new SecurityTokenException("Unknown Username or Password");
}
}
which could be configured as a service behavior:
<system.serviceModel>
<services>
<service behaviorConfiguration="WcfService.Service1Behavior" name="MySamples.WcfService">
<endpoint address="" binding="wsHttpBinding" contract="MySamples.IWcfService" bindingConfiguration="SafeServiceConf">
<identity>
<dns value="localhost"/>
</identity>
</endpoint>
<endpoint address="mex" binding="mexHttpsBinding" contract="IMetadataExchange"/>
</service>
</services>
<behaviors>
<serviceBehaviors>
<behavior name="WcfService.Service1Behavior">
<serviceMetadata httpGetEnabled="true"/>
<serviceDebug includeExceptionDetailInFaults="true" />
<serviceCredentials>
<userNameAuthentication
userNamePasswordValidationMode="Custom"
customUserNamePasswordValidatorType="MySamples.CustomValidator, WcfService"
/>
</serviceCredentials>
</behavior>
</serviceBehaviors>
</behaviors>
<bindings>
<wsHttpBinding>
<binding name="SafeServiceConf" maxReceivedMessageSize="65536">
<readerQuotas maxStringContentLength="65536" maxArrayLength="65536" maxBytesPerRead="65536" />
<security mode="TransportWithMessageCredential">
<message clientCredentialType="UserName" />
</security>
</binding>
</wsHttpBinding>
</bindings>
</system.serviceModel>
and on the client:
using (var client = new WcfServiceClient())
{
client.ClientCredentials.UserName.UserName = "test";
client.ClientCredentials.UserName.Password = "secret";
var result = client.SomeMethod();
}
If I have understood your question correctly,you need a way to provide windows authentication to your WCF service. Following link can help.
Link
After a lot of experimentating I've found a way to do this.
I'll refer to the ASP.NET MVC application as the client.
Through a loginService , the client can pass the username and password. In this service this data is used to check if there is a corresponding windows account. If so a guid is created and this guid along with the username and password (both encrypted) will be stored in a database. The service returns the guid to the client which puts this guid in the session state.
When another call is made to a wcf service, the client sends this guid in a messageheader called 'id' . In wcf I use a ServiceAuthorizationManager to check the incoming messages. If there is a messageheader called 'id', this id will be used to get the corresponding username and password in the database, and with this username and password get the windowsidentity and then impersonate it.(this all happens before it reaches the service itself)
When the user gets to the service, he will be impersonated with his windows account.
I am super new to MVC (in fact, this is my first assignment)
So, I have a good webservice running, functional, on my local machine
http://www.codetrials.local/wcf/UserServices.svc?wsdl
and In my MVC application, I added a service reference as usual, and then in my Model.cs I am trying this:
using (CodeTrials.UserServicesClient _client = new UserServicesClient())
{
UserWebsite = _client.GetUserWebsite(username);
}
but when I try to run this, I always get the exception endpoint not found. I can access this from my (different) asp.net project and it works just fine, same code and everything. After some digging around I found this answer I modified my above code to:
BasicHttpBinding binding = new BasicHttpBinding();
EndpointAddress address = new EndpointAddress("http://www.codetrials.local/wcf/UserServices.svc");
using (CodeTrials.UserServicesClient _client = new UserServicesClient(binding, address))
{
UserWebsite = _client.GetUserWebsite(username);
}
but now, I get a new exception: There was no endpoint listening at http://www.codetrials.local/wcf/UserServices.svc?wsdl that could accept the message
So I am at my wits end.
I found a similar question but it's not what I am looking for.
Can you please guide me to the right path?
what am I not doing right?
should I shift the consuming of webservice from Model to Controller?
Thanks.
EDIT - This is my config file system.serviceModel section. I just copy pasted it from the WCF client test gui tool into web.config since it was not being generated by visual studio.
<system.serviceModel>
<bindings>
<basicHttpBinding>
<binding name="BasicHttpBinding_IUserServices" closeTimeout="00:01:00"
openTimeout="00:01:00" receiveTimeout="00:10:00" sendTimeout="00:01:00"
allowCookies="false" bypassProxyOnLocal="false" hostNameComparisonMode="StrongWildcard"
maxBufferSize="65536" maxBufferPoolSize="524288" maxReceivedMessageSize="65536"
messageEncoding="Text" textEncoding="utf-8" transferMode="Buffered"
useDefaultWebProxy="true">
<readerQuotas maxDepth="32" maxStringContentLength="8192" maxArrayLength="16384"
maxBytesPerRead="4096" maxNameTableCharCount="16384" />
<security mode="None">
<transport clientCredentialType="None" proxyCredentialType="None"
realm="" />
<message clientCredentialType="UserName" algorithmSuite="Default" />
</security>
</binding>
</basicHttpBinding>
</bindings>
<client>
<endpoint address="http://www.codetrials.local/wcf/UserServices.svc/wcf/UserServices.svc"
binding="basicHttpBinding" bindingConfiguration="BasicHttpBinding_IUserServices"
contract="IUserServices" name="BasicHttpBinding_IUserServices" />
</client>
</system.serviceModel>
Reposting from the comments since it turned out to be the answer. :) Turned out that web.debug.config was overwriting the web.config in this case.
Are you sure its in the right web.config then? VS should create it automatically when you add the service reference (it does for me at least). One gotcha is that a normal MVC app has two web.config files, there's a second one inside the Views folder by default. Other then that, I'm not really sure whats going on.
Your code shows you are using this URL: http://www.codetrials.local/wcf/UserServices.svc to access the service endpoint but your exception message says you are actually using http://www.codetrials.local/wcf/UserServices.svc?wsdl instead.
Check your MVC app web.config file for a serviceModel element. If you need to configure the WCF client in code then remove that entire element from the web.config file which may be where the wrong URL is coming from. If you do want to configure WCF from the web.config file, then remove your current code and use the following two lines to create the client and invoke the service:
var _client = new UserServicesClient("BasicHttpBinding_IUserServices");
UserWebsite = _client.GetUserWebsite(username);
where the something like the following section exists in your web.config serviceModel element:
<system.serviceModel>
<client>
<endpoint
name="BasicHttpBinding_IUserServices"
address="http://www.codetrials.local/wcf/UserServices.svc"
binding="basicHttpBinding"
contract="IUserServices" >
</endpoint>
</client>
<!-- rest of element snipped -->
Finally, you should not wrap the UserServicesClient instantiation in a using statement because of the reasons outlined in this post. WCF is a tricksty beast....
EDIT:
Based on the update with your config, your problem may be that the service URL is:
http://www.codetrials.local/wcf/UserServices.svc/wcf/UserServices.svc
The wcf/UserServices.svc seems to be duplicated.