404 (Not Found) for PUT and EDIT after enabling Cors - asp.net-web-api2

I'm using OWIN authentication and testing my Web APIs locally and I got some errors about CORS. I solved the problem just by adding customeHeaders tag in web.config. Now I'm able to login and and also get data. But when I try to PUT or DELETE requests, I get 404 (Not Found) error.
public partial class Startup
{
public void Configuration(IAppBuilder app)
{
ConfigureAuth(app);
}
}
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
config.SuppressDefaultHostAuthentication();
config.Filters.Add(new HostAuthenticationFilter(OAuthDefaults.AuthenticationType));
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
config.Formatters.Remove(config.Formatters.XmlFormatter);
var jsonFormatter = new JsonMediaTypeFormatter();
config.Services.Replace(typeof(IContentNegotiator), new JsonContentNegotiator(jsonFormatter));
}
}
and in web.config
<httpProtocol>
<customHeaders>
<add name="Access-Control-Allow-Origin" value="*" />
<add name="Access-Control-Allow-Methods" value="*" />
<add name="Access-Control-Allow-Headers" value="*" />
</customHeaders>
</httpProtocol>

Related

How to implement custom route in ASP.NET MVC containing dot?

Instead of the conventional MVC route of
{controller}/{action}/{id}
I need my system to handle requests like this
{controller}/{action}.{id}
e.g.
http://localhost:50691/epws/openWebViewer.x551
I have no control over the system calling me
I modified my routeconfig as such
public class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
name: "Default",
url: "{controller}/{action}.{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}
}
with the following controller
public class epwsController : Controller
{
public ActionResult openWebViewer(string id)
{
return View(id);
}
}
but the URL above just returns a 404
You could do something like below, remove action parameter from url with route attributes. Then extend id parameter, so string "openWebViewer.x551" gets inside of the action as a whole and do some inner routing inside controller. Finally add web.config setting to add trailing slash to URL so it does not get handled by static file handler
Controller
using System;
using System.Web.Mvc;
namespace WebApplication8.Controllers
{
[RoutePrefix("epws")]
public class EpwsController : Controller
{
[Route("{id}")]
public ActionResult Index(string id)
{
var parameters = id.Split('.');
switch (parameters[0])
{
case "openWebViewer":
return openWebViewer(parameters[1]);
default:
throw new NotImplementedException();
}
}
public ActionResult openWebViewer(string id)
{
return View(id);
}
}
}
RouteConfig.cs
public class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapMvcAttributeRoutes();
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}
}
Web.config
<configuration>
<system.webServer>
<rewrite>
<rules>
<rule name="Add trailing slash for some URLs" stopProcessing="true">
<match url="^(.*(\.).+[^\/])$" />
<conditions>
<add input="{REQUEST_FILENAME}" matchType="IsFile" negate="true" />
<add input="{REQUEST_FILENAME}" matchType="IsDirectory" negate="true" />
</conditions>
<action type="Redirect" url="{R:1}/" />
</rule>
</rules>
</rewrite>
</system.webServer>
I believe static files will be broken with this web.config, but you could adjust regex to match only specific parameter names, e.g. openWebViewer

Correct way to use Serilog with WebApi2

I am in search of the correct way to use serilog with aspnet webapi2.
As for now I initialize the global Log.Logger property like that :
public static void Register(HttpConfiguration config)
{
Log.Logger = new LoggerConfiguration()
.WriteTo.Elasticsearch(new ElasticsearchSinkOptions(new Uri("http://localhost:9200"))
{
IndexFormat = IndexFormat,
BufferBaseFilename = outputLogPath,
AutoRegisterTemplate = true,
AutoRegisterTemplateVersion = AutoRegisterTemplateVersion.ESv6,
CustomFormatter = new ElasticsearchJsonFormatter(renderMessageTemplate: false),
BufferFileCountLimit = NbDaysRetention
})
.MinimumLevel.ControlledBy(new LoggingLevelSwitch() { MinimumLevel = LogEventLevel.Information})
.Enrich.FromLogContext()
.Enrich.WithWebApiRouteTemplate()
.Enrich.WithWebApiActionName()
.CreateLogger();
//Trace all requests
SerilogWebClassic.Configure(cfg => cfg.LogAtLevel(LogEventLevel.Information));
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
Is there a more cleaner way to do it? I am wondering, if this might be a problem if I have to put some test in place for my controllers.
I have used the following code organization quite a lot for apps with Web API (and/or MVC). (note that it may be a bit outdated, as it is based on old versions of some packages, but you should be able to adapt it without too much work ...)
You will need quite a few packages, that you should be able to guess from the namespaces, but most importantly, install the WebActivatorEx package which provides a way to have code running at different moment of the app lifecycle
Our Global.asax.cs ended up looking like this :
public class WebApiApplication : System.Web.HttpApplication
{
// rely on the fact that AppPreStart is called before Application_Start
private static readonly ILogger Logger = Log.ForContext<WebApiApplication>();
public override void Init()
{
base.Init();
}
protected void Application_Start()
{
// WARNING : Some code runs even before this method ... see AppPreStart
Logger.Debug("In Application_Start");
// Mvc (must be before)
AreaRegistration.RegisterAllAreas();
// Web API
// ... snip ...
// some DependencyInjection config ...
// ... snip ...
// MVC
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
Logger.Information("App started !");
}
protected void Application_End(object sender, EventArgs e)
{
Logger.Debug("In Application_End");
ApplicationShutdownReason shutdownReason = System.Web.Hosting.HostingEnvironment.ShutdownReason;
Logger.Information("App is shutting down (reason = {#shutdownReason})", shutdownReason);
// WARNING : Some code runs AFTER Application_End ... see AppPostShutDown
}
}
and then several classes under the App_Start folder (some of which you can just ignore :P ) :
AppPreStart.cs
using XXX;
using Serilog;
[assembly: WebActivatorEx.PreApplicationStartMethod(typeof(AppPreStart), nameof(AppPreStart.PreApplicationStart))]
namespace XXX
{
/// <summary>
/// This runs even before global.asax Application_Start (see WebActivatorConfig)
/// </summary>
public class AppPreStart
{
public static void PreApplicationStart()
{
LogConfig.Configure();
var logger = Log.ForContext<AppPreStart>();
logger.Information("App is starting ...");
// ... snip ...
// very early things like IoC config, AutoMapper config ...
// ... snip ...
logger.Debug("Done with AppPreStart");
}
}
}
AppPostShutDown.cs
using XXX;
using Serilog;
[assembly: WebActivatorEx.ApplicationShutdownMethod(typeof(AppPostShutDown), nameof(AppPostShutDown.PostApplicationShutDown))]
namespace XXX
{
/// <summary>
/// This runs even before global.asax Application_Start (see WebActivatorConfig)
/// </summary>
public class AppPostShutDown
{
private static ILogger Logger = Log.ForContext<AppPostShutDown>();
public static void PostApplicationShutDown()
{
Logger.Debug("PostApplicationShutDown");
// ... snip ...
// very late things like IoC dispose ....
// ... snip ...
// force flushing the last "not logged" events
Logger.Debug("Closing the logger! ");
Log.CloseAndFlush();
}
}
}
LogConfig.cs
using Serilog;
using Serilog.Events;
using SerilogWeb.Classic;
using SerilogWeb.Classic.Enrichers;
using SerilogWeb.Classic.WebApi.Enrichers;
namespace XXX
{
public class LogConfig
{
static public void Configure()
{
ApplicationLifecycleModule.LogPostedFormData = LogPostedFormDataOption.OnlyOnError;
ApplicationLifecycleModule.FormDataLoggingLevel = LogEventLevel.Debug;
ApplicationLifecycleModule.RequestLoggingLevel = LogEventLevel.Debug;
var loggerConfiguration = new LoggerConfiguration().ReadFrom.AppSettings()
.Enrich.FromLogContext()
.Enrich.With<HttpRequestIdEnricher>()
.Enrich.With<UserNameEnricher>()
.Enrich.With<HttpRequestUrlEnricher>()
.Enrich.With<WebApiRouteTemplateEnricher>()
.Enrich.With<WebApiControllerNameEnricher>()
.Enrich.With<WebApiRouteDataEnricher>()
.Enrich.With<WebApiActionNameEnricher>()
;
Log.Logger = loggerConfiguration.CreateLogger();
}
}
}
and then read the variable parts of the Log configuration from Web.config that has the following keys in AppSettings :
<!-- SeriLog-->
<add key="serilog:level-switch:$controlSwitch" value="Information" />
<add key="serilog:minimum-level:controlled-by" value="$controlSwitch" />
<add key="serilog:enrich:with-property:AppName" value="XXXApp" />
<add key="serilog:enrich:with-property:AppComponent" value="XXXComponent" />
<add key="serilog:enrich:with-property:Environment" value="Dev" />
<add key="serilog:enrich:with-property:MachineName" value="%COMPUTERNAME%" />
<add key="serilog:using:Seq" value="Serilog.Sinks.Seq" />
<add key="serilog:write-to:Seq.serverUrl" value="http://localhost:5341" />
<add key="serilog:write-to:Seq.apiKey" value="xxxxxxxxxxx" />
<add key="serilog:write-to:Seq.controlLevelSwitch" value="$controlSwitch" />
(and then we had Web.config transforms to turn it into a "tokenized" file for production
Web.Release.config
<!-- SeriLog-->
<add key="serilog:enrich:with-property:Environment" value="__Release_EnvironmentName__"
xdt:Transform="Replace" xdt:Locator="Match(key)"/>
<add key="serilog:write-to:Seq.serverUrl" value="__app_serilogSeqUrl__"
xdt:Transform="Replace" xdt:Locator="Match(key)"/>
<add key="serilog:write-to:Seq.apiKey" value="__app_serilogApiKey__"
xdt:Transform="Replace" xdt:Locator="Match(key)"/>
Some of the most important parts of this configuration is :
configure your logger as soon as possible
call Log.CloseAndFlush(); at the very end to be sure all your log events are stored/pushed
add Enrich.FromLogContext() from Serilog, and some enrichers from SerilogWeb.Classic and SerilogWeb.WebApi, they can turn out to be super useful.
logging to a log server that properly supports structured logging (writing to files just has too many drawbacks) ... we used Seq and were very very happy about it (installed locally on every dev machine, and then a centralized instance in production). It supports searching/querying and dashboards and also dynamic log level control.

MVC4 simplemembership not working after publish

I have the following problem with using MVC4 SimpleMembership: Register and Login methods work fine in local debugg mode, but after publish they crash the site. The cause from what i saw is that for example when i register an user the information is stored in my UserProfile table but nothing in webpages_Membership table. So it would appear that the WebSecurity can't access for some reason the webpages_Membership table.
I configured a basic MVC4 project to use SimpleMembership by enabling it in my WebConfig file like this:
<membership defaultProvider="DefaultMembershipProvider">
<providers>
<add name="DefaultMembershipProvider" type="WebMatrix.WebData.SimpleMembershipProvider, WebMatrix.WebData" />
</providers>
</membership>
<roleManager defaultProvider="DefaultRoleProvider">
<providers>
<add name="DefaultRoleProvider" type="WebMatrix.WebData.SimpleRoleProvider, WebMatrix.WebData" />
</providers>
</roleManager>
Added my connectionstring:
<add name="RMBase" providerName="System.Data.SqlClient" connectionString="Data Source=SQL5003.Smarterasp.net;Initial Catalog=DB_9C0558_test;User Id=DB_9C0558_test_admin;Password=*****" />
Initialised the connection in Global.asax:
AreaRegistration.RegisterAllAreas();
WebApiConfig.Register(GlobalConfiguration.Configuration);
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
WebSecurityConfig.InitializeDatabase();
And in my App_Start folder i added the class for WebSecurityConfig
public static void InitializeDatabase()
{
WebSecurity.InitializeDatabaseConnection("RMBase", "UserProfile", "UserID", "UserName", true);
}
Added my DBContext class
public class RMDatabase : DbContext
{
public RMDatabase() : base("RMBase") { }
}
And my Login/Register methods look like this:
[HttpGet]
public ActionResult Login()
{
return View(new Authentication());
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Login(Authentication model)
{
if (!ModelState.IsValid)
{
return View(model);
}
if (WebSecurity.Login(model.Username, model.Password))
{
return RedirectToAction("Index","Home");
}
else
{
ViewBag.ErrorMessage = "incorect";
return View(model);
}
}
[HttpGet]
public ActionResult Register()
{
return View(new Registration());
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Register(Registration model)
{
if (ModelState.IsValid)
{
if (!WebSecurity.UserExists(model.Username))
{
WebSecurity.CreateUserAndAccount(model.Username, model.Password);
WebSecurity.Login(model.Username, model.Password);
return RedirectToAction("Index","Home");
}
ViewBag.ErrorMessage = "incorect";
}
return View(model);
}
Anyone see something that i did wrong that may be the cause of this?
Install Elmah.MVC, then in the Application_Error event in Global.asax, add
var exception = Server.GetLastError();
Elmah.ErrorSignal.FromCurrentContext().Raise(exception);
Then, on the site, navigate to /elmah, which should now have the exception logged for you.
After following Tieson T. advice i found that i had System.Web.Helpers, Version=2.0.0.0 missing from my web project referencess. Added it and all is good. Still don't understand how it was working in local debugg but i am glad that it was fixed.

WebApi PUT does not work when the method has variables MVC 4

I'm new to WebApi and the tutorial was going well. I got Get, Get with variables, Post, and Delete to work, but I can't seem to get Put to work. Well, if I remove all the variables from the method the in the controller, it actually goes into the method, but that's pretty useless. Has anyone come across this before that knows how to fix it?
PUT Url sent to Postman:
http://localhost:60679/api/incident/1234
Preview:
PUT /api/incident/1234 HTTP/1.1
Host: localhost:60679
Cache-Control: no-cache
Error Message:
{"Message":"No HTTP resource was found that matches the request URI
'http://localhost:60679/api/incident/1234'.","MessageDetail":"No action was found on the
controller 'Incident' that matches the request."}
Controller:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Web.Http;
using WebApiTest.AppLogic;
using WebApiTest.Data.Core;
namespace WebApiTest.WebApi.Controllers
{
public class IncidentController : ApiController
{
// GET api/values
public IEnumerable<Incident> Get()
{
using (EntityDemoEntities context = new EntityDemoEntities())
{
return context.Incidents.ToList<Incident>();
}
}
// GET api/values/5
public Incident Get(string id)
{
using (EntityDemoEntities context = new EntityDemoEntities())
{
Guid tempGuid = new Guid(id);
return context.Incidents.SingleOrDefault(i => i.IncidentId == tempGuid);
}
}
// PUT api/values/5
[HttpPut]
public void Put(string guid)
{
HttpResponseMessage result = new HttpResponseMessage();
try
{
if (ModelState.IsValid)
{
//Do something
}
else
{
result = Request.CreateResponse(HttpStatusCode.InternalServerError, "Invalid Model");
}
}
catch (Exception ex)
{
result = Request.CreateResponse(HttpStatusCode.InternalServerError, String.Format("{1}{0}{2}{0}{3}{0}{4}", Environment.NewLine, ex.Message, ex.StackTrace, ex.InnerException == null ? String.Empty : ex.InnerException.Message, ex.InnerException == null ? String.Empty : ex.InnerException.StackTrace));
}
}
}
}
WebApiConfig
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
}
Web.config
<system.webServer>
<validation validateIntegratedModeConfiguration="false" />
<handlers>
<remove name ="WebDAVModule"/>
<remove name="ExtensionlessUrlHandler-ISAPI-4.0_32bit" />
<remove name="ExtensionlessUrlHandler-ISAPI-4.0_64bit" />
<remove name="ExtensionlessUrlHandler-Integrated-4.0" />
<add name="ExtensionlessUrlHandler-ISAPI-4.0_32bit" path="*." verb="GET,HEAD,POST,DEBUG,PUT,DELETE,PATCH,OPTIONS" modules="IsapiModule" scriptProcessor="%windir%\Microsoft.NET\Framework\v4.0.30319\aspnet_isapi.dll" preCondition="classicMode,runtimeVersionv4.0,bitness32" responseBufferLimit="0" />
<add name="ExtensionlessUrlHandler-ISAPI-4.0_64bit" path="*." verb="GET,HEAD,POST,DEBUG,PUT,DELETE,PATCH,OPTIONS" modules="IsapiModule" scriptProcessor="%windir%\Microsoft.NET\Framework64\v4.0.30319\aspnet_isapi.dll" preCondition="classicMode,runtimeVersionv4.0,bitness64" responseBufferLimit="0" />
<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" />
</handlers>
<modules runAllManagedModulesForAllRequests="true">
<remove name='WebDAVModule'/>
</modules>
Change your action to Put(string id) or Put([FromUri(Name="id")]string guid)

ASP.NET MVC 4 WebSecurity Seed

I have some simple DbContext:
public class AuthContext : DbContext
{
public AuthContext() : base("AuthContext")
{
}
public DbSet<User> Users { get; set; }
}
And simple User model:
[Table("User")]
public class User
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int UserId { get; set; }
public string Login { get; set; }
}
What I need is to seed data to WebSecurity always or after model creating. I have tried:
Database.SetInitializer(new AuthDbSeeder());
//--------------------------------------------------------
<add name="AuthContext" connectionString="Data Source=.\SQLEXPRESS; Initial Catalog=ChatAuth; Integrated Security=SSPI; MultipleActiveResultSets=True;" providerName="System.Data.SqlClient" />
and in <system.web> I added:
<roleManager enabled="true" defaultProvider="SimpleRoleProvider">
<providers>
<clear/>
<add name="SimpleRoleProvider" type="WebMatrix.WebData.SimpleRoleProvider, WebMatrix.WebData"/>
</providers>
</roleManager>
<membership defaultProvider="SimpleMembershipProvider">
<providers>
<clear/>
<add name="SimpleMembershipProvider" type="WebMatrix.WebData.SimpleMembershipProvider, WebMatrix.WebData" />
</providers>
</membership>
//--------------------------------------------------------
public class AuthDbSeeder : DropCreateDatabaseAlways<AuthContext>
{
protected override void Seed(AuthContext context)
{
WebSecurity.InitializeDatabaseConnection("AuthContext", "User", "UserId", "Login",
autoCreateTables: true);
WebSecurity.CreateUserAndAccount("Sergey", "1234");
But after all I have error that Database can't be droped because it is already in use. I need some working method to seed data to WebSecurity.
Also very important for me is: how I can add my custom models in the same DbContext and seed data to this context properly.
Also any ideas how I can Unit test WebSecurity?
There is an example on how to seed and customize WebSecurity here.
I have found the solution for the seed question
I reworked SimpleMembershipInitializer:
private class SimpleMembershipInitializer
{
public SimpleMembershipInitializer()
{
try
{
WebSecurity.InitializeDatabaseConnection("AuthContext", "User", "UserId", "Login",
autoCreateTables: true);
}
catch (Exception ex)
{
throw new InvalidOperationException("The ASP.NET Simple Membership database could not be initialized. For more information, please see http://go.microsoft.com/fwlink/?LinkId=256588", ex);
}
}
}
And in my seed method call
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
LazyInitializer.EnsureInitialized(ref initializer, ref isInitialized, ref initializerLock);
}
with null as the parameter.
Unit testing is still a problem