I am having a hard time getting interceptors to work in a MVC application that is hosting WCF services.
I want to add fine grained control over AOP using classes/methods decorated with attributes, but the interceptor is never called using the WCF facility.
In Global.asax I have:
container = new WindsorContainer();
container.AddFacility<WcfFacility>();
container.Kernel.ComponentModelBuilder.AddContributor(new RequireAspects());
container.Install(FromAssembly.This());
RequireAspects wires up the interceptors:
public class RequireAspects : IContributeComponentModelConstruction
{
if (Attribute.IsDefined(model.Implementation, typeof(CacheAttribute)))
{
model.Interceptors.Add(InterceptorReference.ForType(typeof(Caching)));
}
}
Interceptor looks like so:
public class CacheAttribute : Attribute { };
public class Caching : IInterceptor
{
...
}
Service:
[Cache]
public class TestService : ITestService
{
...
}
And finally services are installed:
public class ServicesInstaller : IWindsorInstaller
{
public void Install(Castle.Windsor.IWindsorContainer container,
Castle.MicroKernel.SubSystems.Configuration.IConfigurationStore store)
{
container.Register(AllTypes.FromThisAssembly()
.InNamespace("Test.Services")
.Configure((c => c.LifestyleTransient())));
}
}
Services configuration:
<system.serviceModel>
<services>
<service name="Test.Services.TestService">
<endpoint address=""
binding="webHttpBinding"
contract="Test.Services.ITestService" />
</service>
</services>
<behaviors>
<endpointBehaviors>
<behavior name="">
<enableWebScript />
</behavior>
</endpointBehaviors>
<serviceBehaviors>
<behavior name="">
<serviceMetadata httpGetEnabled="true" />
<serviceDebug includeExceptionDetailInFaults="false" />
</behavior>
</serviceBehaviors>
</behaviors>
<serviceHostingEnvironment aspNetCompatibilityEnabled="true"
multipleSiteBindingsEnabled="true" />
</system.serviceModel>
The interceptor is clearly added and a proxy is created, but the interceptor is never called.
I have reviewed this working example of interceptors with WCF, but doesn't meet my use case.
https://github.com/RussellPolitzky/Castle-Windsor-WCF-Service-With-Interceptor-and-Meta-Data-Publishing
The code above works for all other cases in which I use AOP in MVC and libraries.
Your service method (not shown) is probably not declared as virtual. Interceptors can only work when resolving for an interface or for virtual members on a class.
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
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.
MySettingsViewModel class has only 3 fields. When the the application does a WCF
call it hits the WCF service, fetches data correctly. But upon receiving the object, web client (Asp.NET MVC Web application) throws the following exception.
Error at Webcontroller when receiving WCF response:
The formatter threw an exception while trying to deserialize the
message: There was an error while trying to deserialize parameter
http://tempuri.org/:MySettingsResult. The
InnerException message was 'Error in line 1 position 647. 'EndElement'
'MySettingsResult' from namespace
'http://tempuri.org/' is not expected. Expecting element
'_x003C_Concurrency_x003E_k__BackingField'.'. Please see
InnerException for more details.
[Serializable]
public class MySettingsViewModel
{
public int DefaultCurrencyID { get; set; }
public string TimezoneID { get; set; }
public byte[] Concurrency { get; set; }
}
So it seems that the deserialization of the Concurrency byte array is giving the issue here. When I remove the [Serializable] attribute, code works fine. But I need this to be serializable as I need to send this to the Redis cache.
Here is my WCF settings in the Web client,
<system.serviceModel>
<bindings>
<wsHttpBinding>
<binding name="ExtendedMaxSize" maxReceivedMessageSize="999999999">
<readerQuotas maxDepth="32" maxStringContentLength="5242880" maxArrayLength="2147483646" maxBytesPerRead="4096" maxNameTableCharCount="5242880"/>
</binding>
</wsHttpBinding>
<basicHttpBinding>
<binding name="" maxBufferSize="2147483647" maxBufferPoolSize="2147483647" maxReceivedMessageSize="2147483647">
<readerQuotas maxDepth="2147483647" maxStringContentLength="2147483647" maxArrayLength="2147483647" maxBytesPerRead="2147483647" maxNameTableCharCount="2147483647"/>
</binding>
</basicHttpBinding>
</bindings>
<behaviors>
<serviceBehaviors>
<behavior name="">
<serviceMetadata httpGetEnabled="true"/>
<serviceDebug includeExceptionDetailInFaults="true"/>
</behavior>
</serviceBehaviors>
</behaviors>
<client>
<endpoint address="http://localhost:1800/MyAppManagement.svc" binding="wsHttpBinding" bindingConfiguration="ExtendedMaxSize" contract="MyApp.IAppManagement" name="WSHttpBinding_IApplicationManagement"/>
...some services
</client>
<diagnostics>
<messageLogging logMessagesAtTransportLevel="true" logMessagesAtServiceLevel="false" logMalformedMessages="true" logEntireMessage="true" maxSizeOfMessageToLog="65535000" maxMessagesToLog="500"/>
</diagnostics>
<serviceHostingEnvironment aspNetCompatibilityEnabled="true" multipleSiteBindingsEnabled="true"/>
</system.serviceModel>
WCF settings (service side),
<system.serviceModel>
<bindings>
<wsHttpBinding>
<binding name="ExtendedMaxSize" maxReceivedMessageSize="999999999">
<readerQuotas maxDepth="32" maxStringContentLength="5242880" maxArrayLength="2147483646" maxBytesPerRead="4096" maxNameTableCharCount="5242880"/>
</binding>
</wsHttpBinding>
</bindings>
<behaviors>
<serviceBehaviors>
<behavior>
<serviceMetadata httpGetEnabled="true" httpsGetEnabled="true" />
<serviceDebug includeExceptionDetailInFaults="true" />
</behavior>
</serviceBehaviors>
</behaviors>
<services>
<service name="MyApp.Services.MyAppManagement">
<endpoint address="" binding="wsHttpBinding" bindingConfiguration="ExtendedMaxSize" contract="MyApp.IAppManagement" />
<endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" />
</service>
Other services...
</services>
<protocolMapping>
<add binding="basicHttpsBinding" scheme="https" />
</protocolMapping>
<serviceHostingEnvironment aspNetCompatibilityEnabled="true" multipleSiteBindingsEnabled="true" />
</system.serviceModel>
Sorted out the issue. Adding [DataMember] to "public byte[] Concurrency { get; set; }" made it working.
I've create a web service. After being up for a period of time, I eventually get the follow error: "The open operation did not complete within the allotted timeout of 00:01:00. The time allotted to this operation may have been a portion of a longer timeout."
I'm unsure why I'm getting this error since I've set the open timeout to about 2 hours. I'm new to WCF web services and web services in general. Is there any red flags with the configuration I've shown below?
.NET version: 4.0
Client-side configuration:
<system.serviceModel>
<bindings>
<netTcpBinding>
<binding name="SecurityServiceDev" receiveTimeout="02:00:00" openTimeout="02:00:00" maxBufferPoolSize="2147483647" maxReceivedMessageSize="2147483647" maxConnections="1000" maxBufferSize="2147483647">
<security mode="Transport">
<transport clientCredentialType="Windows" />
</security>
</binding>
</netTcpBinding>
</bindings>
<diagnostics>
<messageLogging logEntireMessage="true" maxMessagesToLog="300" logMessagesAtServiceLevel="true" logMalformedMessages="true" logMessagesAtTransportLevel="true" />
</diagnostics>
<client>
<endpoint address="net.tcp://XXX.svc" binding="netTcpBinding" bindingConfiguration="SecurityServiceDev" contract="SecurityProxy.ISecurityService" name="SecurityService" />
</client>
Web Service Configuration:
<system.serviceModel>
<services>
<service name="TMS.DAL.Common.Security.BAL.SecurityService" behaviorConfiguration="DataServiceBehavior">
<endpoint address="net.tcp://XXX.svc"
name="SecurityService"
binding="netTcpBinding"
bindingConfiguration="Binding"
contract="TMS.DAL.Common.Security.BAL.ISecurityService"/>
<endpoint contract="IMetadataExchange" binding="mexHttpBinding" address="mex" />
</service>
</services>
<bindings>
<netTcpBinding>
<binding name="Binding"
receiveTimeout="02:00:00"
openTimeout="02:00:00"
maxBufferPoolSize="2147483647"
maxReceivedMessageSize="2147483647"
maxConnections="1000"
maxBufferSize="2147483647">
<security mode="Transport">
<transport clientCredentialType="Windows"/>
</security>
</binding>
</netTcpBinding>
</bindings>
<client/>
<behaviors>
<serviceBehaviors>
<behavior name="DataServiceBehavior" >
<serviceDebug includeExceptionDetailInFaults="true"/>
<serviceMetadata httpGetEnabled="true" />
<dataContractSerializer maxItemsInObjectGraph="61200000" />
<serviceThrottling maxConcurrentCalls="1000"
maxConcurrentInstances="2147483647" maxConcurrentSessions="1000"/>
</behavior>
</serviceBehaviors>
</behaviors>
The only configuration inside the code is the following:
[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall, ConcurrencyMode = ConcurrencyMode.Multiple)]
public class SecurityService : ISecurityService
Service Contract:
[ServiceContract]
public interface ISecurityService
{
[OperationContract]
bool IsUserInRole(String userName, String roleName);
[OperationContract]
bool DoesUserHavePermission(String userName, String permissionName);
[OperationContract]
List<String> GetUsersInRole(String roleName);
[OperationContract]
List<String> GetRolesForUser(String userName);
[OperationContract]
List<String> GetPermissionsForUser(String userName);
[OperationContract]
List<String> GetPermissionsForRole(String roleName);
[OperationContract]
List<String> GetRolesForApplication(String appName);
[OperationContract(Name = "GetListValuesForRoleAndList")]
List<String> GetListValues(String roleName, String listNames);
[OperationContract(Name = "GetListValuesForRolesAndList")]
List<String> GetListValues(List<String> roleNames, List<String> listNames);
[OperationContract(Name = "GetListValuesForAppUserList")]
List<String> GetListValues(String appName, String userName, List<String> listNames);
[OperationContract]
List<String> GetListsForUser(String userName);
[OperationContract]
bool RoleExists(String roleName);
[OperationContract]
bool ExternalUserExists(String userName, String password);
Each page gets created anew for the request and then discarded - NetTcpBinding is inherently sessionful which means it keeps resources around until the client says they no longer need them or the client goes away for, in your case, 2 hours as you have set the receiveTimeout to 2 hours.
As you don't call Close WCF thinks the session is still alive and so will throttle the requests from the new pages until old sessions start timing out. The .NET 4 default session throttle is fairly high (100 x number of cores) and you have set it yourself at 1000 so you will not see this problem until the processing ahs been running some time. In some ways the default session throttle in .NET 3.5 was better at 10 as it became very obvious very quickly if you were in the situation you are in
I wrote a blog post about sessions a while back
I'm starting a new project for work, and I decided I want to give MVC a shot. It's a small internal site for a commute challenge.
I want to use Spring.NET for Validation. I have used Spring.NET before in Web Forms, but with no code behind as in traditional ASP.NET, how do I use the Page Validation framework Spring.NET provides?
Edit 1:
In an attempt to try this myself, here is what I have:
Web.Config
<?xml version="1.0"?>
<configuration>
<configSections>
<sectionGroup name="spring">
<section name="context" type="Spring.Context.Support.WebContextHandler, Spring.Web" />
<section name="objects" type="Spring.Context.Support.DefaultSectionHandler, Spring.Core"/>
<section name="parsers" type="Spring.Context.Support.NamespaceParsersSectionHandler, Spring.Core"/>
</sectionGroup>
</configSections>
<appSettings>
<add key="RouteValidator" value="RouteValidator"/>
<add key="UserValidator" value="UserValidator"/>
</appSettings>
<spring>
<context>
<resource uri="config://spring/objects"/>
<resource uri="~/Config/Spring.Web.cfg.xml" />
<resource uri="~/Config/Spring.Validation.cfg.xml" />
</context>
<parsers>
<parser type="Spring.Validation.Config.ValidationNamespaceParser, Spring.Core" />
</parsers>
</spring>
<system.web>
<httpModules>
<add name="Spring" type="Spring.Context.Support.WebSupportModule, Spring.Web" />
</httpModules>
</system.web>
</configuration>
Spring.Web.Cfg.xml
<?xml version="1.0" encoding="utf-8" ?>
<objects xmlns="http://www.springframework.net"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.net http://www.springframework.net/xsd/spring-objects.xsd">
<description>
Foo MVC Controller declarations.
</description>
<object id="HomeController" type="Foo.MVC.Web.Controllers.HomeController, Foo.MVC.Web"></object>
<object id="AccountController" type="Foo.MVC.Web.Controllers.RouteController, Foo.MVC.Web"></object>
<object id="RouteController" type="Foo.MVC.Web.Controllers.RouteController, Foo.MVC.Web"></object>
<object id="Spring.Web.UI.Controls.ValidationError" abstract="true">
<property name="Renderer">
<object type="Spring.Web.UI.Validation.IconValidationErrorsRenderer, Spring.Web">
<property name="IconSrc" value="validation-error.gif"/>
</object>
</property>
</object>
<object id="Spring.Web.UI.Controls.ValidationSummary" abstract="true">
<property name="Renderer">
<object type="Spring.Web.UI.Validation.DivValidationErrorsRenderer, Spring.Web">
<property name="CssClass" value="validationError"/>
</object>
</property>
</object>
<object id="standardPage" abstract="true">
<property name="MasterPageFile" value="~/Views/Shared/Site.master"/>
<property name="CssRoot" value="~/Content/"/>
<property name="ImagesRoot" value="~/Content"/>
</object>
</objects>
My validation file is very standard and basically a copy and paste from another project, therefore I didn't include it.
Now the problem I have is how do I use it? How do I get application context? My web forms project users Spring.Web.UI.Page, but I'm worried because the default pages in MVC derive from System.Web.Mvc.ViewPage, so that isn't going to work.
Or am I just not able to use Spring.NET's framework for MVC quite yet?
Thanks!
Thanks for any assistance.
You definitely can use Spring with ASP.Net MVC. You need to register that you are using it in the Global.ascx class then the framework will create Controllers based on what you have defined in your config file.
public class MvcApplication : System.Web.HttpApplication
{
...Routes stuff...
protected void Application_Start()
{
ControllerBuilder.Current.SetControllerFactory(typeof(ControllerFactory));
RegisterRoutes(RouteTable.Routes);
}
}
public class ControllerFactory : IControllerFactory
{
public IController CreateController(RequestContext requestContext, string controllerName)
{
return IoC.Resolve<IController>(controllerName);
}
public void ReleaseController(IController controller)
{
//This is a sample implementation
//If pooling is used write code to return the object to pool
if (controller is IDisposable)
{
(controller as IDisposable).Dispose();
}
controller = null;
}
}
public static class IoC
{
static readonly IObjectFactory Factory
= new XmlObjectFactory(new FileSystemResource
(HttpContext.Current.Server.MapPath("~/Config/Spring.config")));
public static T Resolve<T>(string name)
{
return (T)Factory.GetObject(name);
}
}
Just make sure that the path to your spring config file is correct! This was adapted from this link.
On a wider note, this approach does not allow you to Spring the page classes, and being an MVC architecture, where views are pretty dumb classes, does not really support rich validation in the view itself in the manner you suggest. Look at either including the validation in the Model (post-back) in JQuery.
In best of my knowledge, the Spring Validation is not supported up to the latest release of ASP.NET MVC (1.0) and Spring.NET framework (1.3).
As far as incorporating Spring.NET with MVC, you can use MvcContrib project and come to the same code-base as posted by Colin Desmond (but you don't have to do the dirty work yourself).