Eliminate the .svc in the URL of a WCF 4 service using Routes? - wcf

I'm using WCF 4 on IIS 7.5 and want to eliminate the default .svc extension from the URL of all my RESTful services. I've seen the approaches documented using the Url Rewrite Module and an IHttpModule but I don't want to take those approaches.
I am vaguely familiar with the concept of Routes introduced in ASP.NET MVC and as I understand they are now abstracted out of MVC in Net 4 as System.Web.Routing. But in looking at the docs it appears I need to add a Global.asax file to my project which I'm not really keen on. Is there any other way to handle this?
I've also seen the Configuration-Based activation feature, but that only seems to eliminate the .svc file, but still requires I use .svc in the url of my service.
Can anyone summarize my options here for not needing .svc in my urls?

Sure, no problem: first off, read all about the new WCF 4 features in A Developer's Introduction to Windows Communication Foundation 4.
What you're looking for is called file-less service activation. It's a new setting in your <system.serviceModel> that looks something like this:
<serviceHostingEnvironment>
<serviceActivations>
<add factory="System.ServiceModel.Activation.ServiceHostFactory"
relativeAddress="[subfolder/]filename.svc" or "~/[subfolder/]filename.svc"
service="SomeNamespace.YourService"/>
</serviceActivations>
</serviceHostingEnvironment>
Basically, all the information you'd have in the *.svc file (path, service to call) is in this config section now.
You should be able to call this service at
http://yourserver/virtualdirectory/YourService
now - no more *.svc, no messy URL rewriting etc. - it just plain works!
Update: it doesn't seem to work that well, unless you go in and add a *.svc extension to your relative path - kind defeats the whole purpose!
If you want to register using an ASP.NET route, check out the MSDN docs on that topic. You'd have to have something like this in your application startup, in a web app that would be global.asax.cs:
void Application_Start(object sender, EventArgs e)
{
RegisterRoutes(RouteTable.Routes);
}
private void RegisterRoutes(RouteCollection routes)
{
routes.Add(new ServiceRoute("YourService",
new WebServiceHostFactory(), typeof(SomeNamespace.YourService)));
}
Hopefully, with that, you'll be able to get your service up and running without any *.svc file extensions!

Just to wrap up the answer and add one more point there. You'll need the same ASP.NET route registration as above:
void Application_Start(object sender, EventArgs e)
{
RegisterRoutes(RouteTable.Routes);
}
private void RegisterRoutes(RouteCollection routes)
{
routes.Add(new ServiceRoute("YourService",
new WebServiceHostFactory(), typeof(SomeNamespace.YourService)));
}
In order to get this working you need however to add some more things to web.config. The service hosting should be configured to be ASP.NET compatible. This can be done by adding aspNetCompatibiliyEnabled="true" to serviceHostingEnvironment element:
<serviceHostingEnvironment multipleSiteBindingsEnabled="true" aspNetCompatibilityEnabled="true" />
Hope this clarifies and gives an easier to find solution.

Important clarification for IIS 7.5 and Framework 4.0:
For the Web routing module to work, it requires the application pool set to "Integrated Mode", it doesn't works in "Classic Mode"
One additional problem I've found after switching is that when in "Integrated Mode" the application crashed on startup because I had modules included in the <system.Web> section.
I fixed it by moving the modules to the <system.webServer> configuration section that is new for IIS 7

Related

My Entity Framework Core API works fine locally, but fails with a 405 error in production

I have a .NetCore 3.1 API project that uses Entity Framework.
It is working perfectly when I run it locally from Visual Studio.
However, after publishing to a production IIS 8.5 server, I get the following errors when I try to hit an API that uses PUT on the production server.
xhr.js:178 PUT https://nationalparks.xyz.gov/api/plants/91 405 (Method
Not Allowed)
My controller begins like this:
[HttpPut("{id}")]
public async Task<IActionResult> PutPlant(long id, [FromBody] Plant plant)
{
...
}
And client-side looks like this:
await axios({
method: "PUT",
url: "api/plants/" + plant.id,
data: JSON.stringify(plant),
headers: { 'Content-Type': 'application/json; charset=utf-8' }
});
Honestly I'm stumped...I'm not sure why it's doing that.
I've seen a few posts saying to modify the web.config, but my app uses appsettings.json, not a web.config.
Any ideas?
Thanks!
The reason behind the issue is the WebDAV module intercepted the request. to resolve the issue you could follow the below workaround.
1)Remove WebDAV from your system:
open control panel-> “Turn Windows Features On or Off”->un-ticking the WebDAV publishing checkbox.
or
2)Add below code in your web.config file:
<system.webServer>
<modules>
<remove name="WebDAVModule" />
</modules>
<handlers>
<remove name="WebDAV" />
</handlers>
</system.webServer>
Please remember that after doing these changes you can nit use the WebDAV module in iis.
dotnet core applications do use web.config when running under IIS. You will notice that the publish command makes a web.config file that tells IIS which DLL to run for your application.
I think I had the same issue a while back. I fixed it by adding lines to web.config to remove the WebDAV modules. See this answer for more detail:
https://stackoverflow.com/a/12443578/65432

Running WebApi alongside MVC project in Azure

I have a solution with an MVC project and also a separate Web Api project. I have been working on the the MVC portion and have had it hosted as a web role in a cloud service project.
Now I need to build on the Web Api. Running the solution as is in the azure emulator I'm unable to hit my Web Api controller, I get a 404 Not Found.
What steps do I need to take after creating the WebApi project in the solution in order to enable my MVC project alongside it to hit the Api controller using a jQuery ajax call (from the MVC project)?
Note: The portion of the application that will be consuming the Api is in a subdomain (ex "consumingapp.myapplication.com/api/Values". I don't know if that makes any difference.
Ok here are the steps to run MVC and WebApi side by side in Azure under a single web role:
Add only the MVC project as a Web Role in the Cloud Service project.
Open ServiceDefinition.csdef an configure as follows:
<Sites>
<Site name="Web" physicalDirectory="..\..\..\Application.Web">
<Bindings>
<Binding name="Endpoint1" endpointName="Endpoint1" />
</Bindings>
</Site>
<Site name="Web.Api" physicalDirectory="..\..\..\Application.Web.Api">
<Bindings>
<Binding name="Endpoint1" endpointName="Endpoint1" hostHeader="api.myapplication.com"/>
</Bindings>
</Site>
</Sites>
Note:
The physical directory is relative to the definition file that is deployed to CloudProject\bin\release so you need to back track 3 nodes to get to the solution folder. Also I had some trouble with this trying to run the project it would automatically switch the physicalDirectory back to a bad path. To fix that I right clicked the cloud project > Project Dependencies > make sure both projects are checked off. Also make sure to save and close all instances of the text editor for the definition file then Rebuild and finally Run. Seems like some kind of bug that was driving me nuts and eventually disappeared.
Configure your host files to allow for local testing using the emulator:
127.0.0.1 myapplication.com api.myapplication.com
To use this .NET cors implementation run the following Nuget command on your WebApi project:
Install-Package Thinktecture.IdentityModel
Add the following class (taken from the post) to your WebApi project:
public static class CorsConfig
{
public static void RegisterCors(HttpConfiguration httpConfiguration)
{
WebApiCorsConfiguration corsConfig = new WebApiCorsConfiguration();
corsConfig.RegisterGlobal(httpConfiguration);
corsConfig
.ForResources("Values") //Controller nae
.ForOrigins("http://myapplication.com", "myapplication.com:81")
.AllowAll();
}
}
Call RegisterCors() from Application_Start()
Make sure you Web.config in the WebApi project has OPTIONS verb for the ExtensionlessUrlHandler:
<add name="ExtensionlessUrlHandler-Integrated-4.0" path="*." verb="GET,HEAD,POST,DEBUG,PUT,DELETE,PATCH,OPTIONS" type="System.Web.Handlers.TransferRequestHandler" preCondition="integratedMode,runtimeVersionv4.0" />
Make cross-origin request to hit your api controller:
$.ajax({ url: "http://api.myapplication.com/api/Values", ... })
And Viola!
Hope that helps others

CORS with WebAPI for XmlHttpRequest

Just wanted to know what is the best elegant way (currently available) to handle CORS (Cross-Origin Resource Sharing) in ASP.NET WebAPI so i can use XmlHttpRequest over multiple domains ? How i can integrate this in the headers of every type of request (GEt, POST, etc..) with OPTIONS too ?
Thanks!
Tpeczek have a nice found, however while doing my own research ive found something similar and also very elegant ways of handling CORS which enable you to configure your CORS in a config file in App_Start folder. Its all handled using an open source library called Thinkecture. See details here :
http://brockallen.com/2012/06/28/cors-support-in-webapi-mvc-and-iis-with-thinktecture-identitymodel/
It have many advantages.. you can configure origins, methods (GET, POST, etc.), access to specifics controllers and actions and it also keep your controllers clean from any attributes.
WebAPI, IIS and ASP.NET MVC is supported !
Carlos Figueira has a nice series of posts about CORS and ASP.NET Web API:
Implementing CORS support in ASP.NET Web APIs
Implementing CORS support in ASP.NET Web APIs – Take 2
CORS support in ASP.NET Web API – RC version
Personally I'm a big fan of Take 2 approach because EnableCors attribute can be easly extended to give you control over allowed origins.
Add below in web.cofig file(inside the system.webserver element).
<httpProtocol>
<customHeaders>
<add name="Access-Control-Allow-Headers" value="accept, maxdataserviceversion, origin, x-requested-with, dataserviceversion" />
<add name="Access-Control-Allow-Origin" value="*" />
<add name="Access-Control-Max-Age" value="1728000" />
</customHeaders>
</httpProtocol>
and add below code in global.aspx.cs file
protected void Application_BeginRequest(object sender, EventArgs e)
{
if (Request.Headers.AllKeys.Contains("Origin") && Request.HttpMethod == "OPTIONS")
{
Response.StatusCode = 200;
Response.End();
}
}
There is now CORS support in the nightly of web api
http://blogs.msdn.com/b/yaohuang1/archive/2013/04/05/try-out-asp.net-web-api-cors-support-using-the-nightly-builds.aspx
Use nuget to:
Uninstall the Microsoft.AspNet.Mvc.FixedDisplayModes package.
Install Microsoft.AspNet.WebApi.Cors package from the nightly builds
Then fix the bindings in the web.config
Then enable CORS
config.EnableCors(new EnableCorsAttribute())
Read more about it on this wiki https://aspnetwebstack.codeplex.com/wikipage?title=CORS%20support%20for%20ASP.NET%20Web%20API&referringTitle=Specs
Edit 19-04-2013 Scott Guthrie has blogged about it: http://weblogs.asp.net/scottgu/archive/2013/04/19/asp-net-web-api-cors-support-and-attribute-based-routing-improvements.aspx
It depends how fine-grained you want to control CORS. If you want to allow any domain for instance you can add static CORS headers to all responses by configuring them in IIS. I chose this approach and wrote about it here.

Unable to change Culture in WCF

I'm running on a windows 2008 server. I have one Web service which calls a wcf service.
Within the WCF service it attempts to cast a date 20/08/2010 which fails because it thinks it in US format not Austrlaian.
So far I have:
On control panel change the region to English Australian under format
Under the Administrative tab I have also set system local to English (Austrlian)
within IIS7 at the default web site level I have changed Culture and UI culture under the .Net globalization.
I've also done this at the Web service and WCF Nodes
I have added the following to the Web service and WCF apps web.config file
<globalization requestEncoding="utf-8"
responseEncoding="utf-8"
culture="en-AU"
uiCulture="en-AU" />
This finally changed the culture in the Web service but the WCF service remains US culture.
Can anyone tell me what else I can try?
The WCF will ignore your globalization configuration if you do not set aspNet compatibility:
<system.serviceModel>
<serviceHostingEnvironment aspNetCompatibilityEnabled="true"/>
...
To use that mode your service class must have the attribute AspNetCompatibilityRequirements set to Allowed or Required:
[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
public class ServiceClass
{
...
}
This could work if you want to apply the Culture and CultureUI from config file.
Or you could either try to force the Culture in your WCF service code, if you are sure that it will not change dynamically. For instance, in your service class constructor. Note that this is not a best practice, perhaps you should use a Context initializer, but this one is quite simple.
public ServiceClass()
{
...
System.Threading.Thread.CurrentThread.CurrentCulture = new CultureInfo("en-AU");
System.Threading.Thread.CurrentThread.CurrentUICulture = new CultureInfo("en-AU");
}
More info:
setting-cultureinfo-on-wcf-service-calls
using-call-context-initializers-for-culture
The problem is in the culture that is set for a user used in the application pool.
I found the following way to resolve this issue:
If the application pools uses ApplicationPoolIdentity change it to NETWORKSERVICE (unfortunatly I didn't found how to set regional settings for ApplicationPoolIdentity)
Set regional settings you need (en-AU) on the current user and than copy them for the system accounts as described here.
You can do it in the Global.asax.cs file, in the Application_Start file:
using System.Threading;
using System.Globalization;
public class Global : HttpApplication
{
protected void Application_Start(object sender, EventArgs e)
{
CultureInfo.DefaultThreadCurrentCulture = CultureInfo.DefaultThreadCurrentUICulture = new CultureInfo("en-AU");
}
}

How to host Web API in Windows Service

I have several resources that I'd like to expose using the WCF Web API. I've investigated the Web API using a Web host but our services all run as Windows Services in production so it's time for me to put the tests aside and verify that everything will work as we need it. I've looked as the sample app here: http://webapicontrib.codeplex.com/SourceControl/changeset/view/2d771a4d6f6f#Samples%2fSelfHosted%2fserver%2fProgram.cs but this does not work with the current version (preview 5) because the HttpConfigurableServiceHost class is not accessible from our code.
One of the most appealing aspects of the Web API is the simple startup using MapServiceRoute and the new WebApiConfiguration. I don't see, however, a way to define the base url and port for the services. Obviously, hosting the service in IIS eliminates this because we configure this information in IIS. How can I accomplish this when hosting in a Windows Service?
It's actually pretty simple. In a nutshell you need to instantiate HttpSelfHostServer and HttpSelfHostConfiguration and then call server.OpenAsync().
public void Start()
{
_server.OpenAsync();
}
public void Stop()
{
_server.CloseAsync().Wait();
_server.Dispose();
}
For an example on how to do this using Windows service project template and/or Topshelf library see my blog post: http://www.piotrwalat.net/hosting-web-api-in-windows-service/
The latest version just uses HttpServiceHost. http://webapicontrib.codeplex.com/SourceControl/changeset/view/ddc499585751#Samples%2fSelfHosted%2fserver%2fProgram.cs
Ping me on twitter if you continue to have problems.
This is the basic code using a console app. A Windows Service uses the same basic approach except you use the start and stop methods to start and stop the service and don't need to block.
static void Main(string[] args)
{
var host = new HttpServiceHost(typeof(PeopleService), "http://localhost:8080/people");
host.Open();
foreach (var ep in host.Description.Endpoints)
{
Console.WriteLine("Using {0} at {1}", ep.Binding.Name, ep.Address);
}
Console.ReadLine();
host.Close();
}
See this blog post.