ASP.NET Web Api Routing (IIS vs Self Hosted) - asp.net-mvc-routing

I have found a minor difference in the routing base classes in ASP.NET Web Api which has forced me to write a little helper class which will allow me to define my routes just once. Is there a reason for this? I'm assuming it was too big a change to the framework to make both RouteCollections derive from the same base class or implement the same interface (which would have made this class much simpler)
public static class RouteMapper
{
private class Route
{
public string Name { get; set; }
public string Template { get; set; }
public object Defaults { get; set; }
public Route(string name, string template, object defaults)
{
Name = name;
Template = template;
Defaults = defaults;
}
}
private static List<Route> GetRoutes()
{
return new List<Route>
{
new Route(
"API Default",
"api/{controller}/{id}",
new {id = RouteParameter.Optional})
};
}
public static void AddHttpRoutes(this HttpRouteCollection routeCollection)
{
var routes = GetRoutes();
routes.ForEach(route => routeCollection.MapHttpRoute(route.Name, route.Template, route.Defaults));
}
public static void AddHttpRoutes(this RouteCollection routeCollection)
{
var routes = GetRoutes();
routes.ForEach(route => routeCollection.MapHttpRoute(route.Name, route.Template, route.Defaults));
}
}
What this allows me to do is to call a simple AddHttpRoutes method in both my Global.asax and my integration tests.
Integration Tests
var configuration = new HttpSelfHostConfiguration("http://localhost:20000");
configuration.Routes.AddHttpRoutes();
_server = new HttpSelfHostServer(configuration);
_server.OpenAsync().Wait();
Global.asax
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.AddHttpRoutes();
routes.MapRoute(
"Default",
"{controller}/{action}/{id}",
new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}
Is this a known issue and is it likely to be fixed in a later release of ASP.NET Web Api?

Yes, the routing is slightly different due to the fact that ASP.NET already has routing but we couldn'd depend on it directly since that would prevent Self-host support. We're still looking at how things could make more sense.

Related

Model Attribute binding in PUT Web API not wokring - ASP.NET Core 3.1

I have a PUT Rest API that I want to do binding from both body and route parameters.
Code
[HttpPut("{Id}/someStuffApi")]
public ActionResult UpdateStatus([FromBody] StatusRequest StatusRequest)
{
// code ...
}
And the model class is
public class StatusRequest
{
[FromRoute(Name = "Id")]
[Required(ErrorMessage = "'Id' attribute is required.")]
public string Id { get; set; }
[FromBody]
[Required(ErrorMessage = "'Status' attribute is required.")]
public string Status { get; set; }
}
When I made a request to this API, the Id is not mapped to the model even though I added the FromRoute attribute explicitly. Any suggestions?
The [FromBody] model binding will effectively override the [FromRoute] option in your model class. This is by design (why, I'm not sure, but an MS decision). See the "[FromBody] attribute" section of this doc: https://learn.microsoft.com/en-us/aspnet/core/mvc/models/model-binding. As pointed out there: "When [FromBody] is applied to a complex type parameter, any binding source attributes applied to its properties are ignored." So adding the "[FromRoute]" attribute inside your model does nothing...it's ignored. You can remove both of those attributes from your model.
So the way around this is to put the route binding in the Put action as a method parameter and then manually add it to your model in the controller before using the model.
[HttpPut("{Id}/someStuffApi")]
public ActionResult UpdateStatus(int Id, [FromBody] StatusRequest StatusRequest)
{
StatusRequest.Id = Id;
// remaining code...
}
The downside to this method is that the Required attribute cannot remain on the Id parameter. It will be null at the time of model binding and if you have .Net Core 3.1 automatic model validation active, then that will always return a 422. So if you would have to manually check that yourself before adding it to the model.
If you want even more flexibility, you can look at something like the HybridModelBinding NuGet package that allows various combinations of model binding using attributes. But this is a 3rd party dependency that you may not want. (https://github.com/billbogaiv/hybrid-model-binding/)
You can use custom model binding,here is a demo:
TestModelBinderProvider:
public class TestModelBinderProvider : IModelBinderProvider
{
private readonly IList<IInputFormatter> formatters;
private readonly IHttpRequestStreamReaderFactory readerFactory;
public TestModelBinderProvider(IList<IInputFormatter> formatters, IHttpRequestStreamReaderFactory readerFactory)
{
this.formatters = formatters;
this.readerFactory = readerFactory;
}
public IModelBinder GetBinder(ModelBinderProviderContext context)
{
if (context.Metadata.ModelType == typeof(StatusRequest))
return new StatusBinder(formatters, readerFactory);
return null;
}
}
Startup.cs:
services.AddMvc()
.AddMvcOptions(options =>
{
IHttpRequestStreamReaderFactory readerFactory = services.BuildServiceProvider().GetRequiredService<IHttpRequestStreamReaderFactory>();
options.ModelBinderProviders.Insert(0, new TestModelBinderProvider(options.InputFormatters, readerFactory));
});
StatusBinder:
public class StatusBinder: IModelBinder
{
private BodyModelBinder defaultBinder;
public StatusBinder(IList<IInputFormatter> formatters, IHttpRequestStreamReaderFactory readerFactory)
{
defaultBinder = new BodyModelBinder(formatters, readerFactory);
}
public async Task BindModelAsync(ModelBindingContext bindingContext)
{
// callinng the default body binder
await defaultBinder.BindModelAsync(bindingContext);
if (bindingContext.Result.IsModelSet)
{
var data = bindingContext.Result.Model as StatusRequest;
if (data != null)
{
var value = bindingContext.ValueProvider.GetValue("Id").FirstValue;
data.Id = value.ToString();
bindingContext.Result = ModelBindingResult.Success(data);
}
}
}
}
StatusRequest:
public class StatusRequest
{
[Required(ErrorMessage = "'Id' attribute is required.")]
public string Id { get; set; }
[Required(ErrorMessage = "'Status' attribute is required.")]
public string Status { get; set; }
}
Action:
[HttpPut("{Id}/someStuffApi")]
public ActionResult UpdateStatus(StatusRequest StatusRequest)
{
return Ok();
}
result:

ReferenceLoopHandling.Ignore not working for Azure Mobile App Service to ignore serialization of circular references

I'm using the following code in my startup class to prevent errors serializing my entities which may cause circular references, but it is not working.
Why?
public partial class Startup
{
public static void ConfigureMobileApp(IAppBuilder app)
{
HttpConfiguration config = new HttpConfiguration();
new MobileAppConfiguration()
.UseDefaultConfiguration()
.ApplyTo(config);
config.MapHttpAttributeRoutes();
// Use Entity Framework Code First to create database tables based on your DbContext
Database.SetInitializer(new MobileServiceInitializer());
MobileAppSettingsDictionary settings = config.GetMobileAppSettingsProvider().GetMobileAppSettings();
config.Formatters.JsonFormatter.SerializerSettings.Re‌​ferenceLoopHandling = ReferenceLoopHandling.Ignore;
config.Services.Add(typeof(IExceptionLogger), new AiExceptionLogger());
if (string.IsNullOrEmpty(settings.HostName))
{
app.UseAppServiceAuthentication(new AppServiceAuthenticationOptions
{
// This middleware is intended to be used locally for debugging. By default, HostName will
// only have a value when running in an App Service application.
SigningKey = ConfigurationManager.AppSettings["SigningKey"],
ValidAudiences = new[] { ConfigurationManager.AppSettings["ValidAudience"] },
ValidIssuers = new[] { ConfigurationManager.AppSettings["ValidIssuer"] },
TokenHandler = config.GetAppServiceTokenHandler()
});
}
app.UseWebApi(config);
}
}
According to your description, I created my Azure Mobile App project to test this issue. Based on your Startup.cs, I added my apiController as follows:
[MobileAppController]
public class ValuesController : ApiController
{
[Route("api/values")]
public HttpResponseMessage Get()
{
Department sales = new Department() { Name = "Sales" };
Employee alice = new Employee() { Name = "Alice", Department = sales };
sales.Manager = alice;
return Request.CreateResponse(sales);
}
}
public class Employee
{
public string Name { get; set; }
//[JsonIgnore]
public Department Department { get; set; }
}
public class Department
{
public string Name { get; set; }
public Employee Manager { get; set; }
}
When access this endpoint, I encountered the following XML Circular Object References error:
Note: For a simple way, I removed the XML Formatter via config.Formatters.Remove(config.Formatters.XmlFormatter);. Also, you could refer to the section about preserving object references in XML from Handling Circular Object References.
After I removed XML Formatter, then I encountered the following error about object references loop in JSON:
Then, I followed this Loop Reference handling in Web API code sample, but without luck in the end. Also, I tried to create a new Web API project and found that ReferenceLoopHandling.Ignore could work as expected.
In the end, I found that if I remove the MobileAppController attribute from my
apiController, then it could work as follows:
In summary, I assumed that you could try to ignore the reference attributes with the JsonIgnore for JSON.NET, for more details you could refer to fix 3:Ignore and preserve reference attributes.

ASP MVC EF6 Multi Tenant based on host

Sorry, another multi tenancy post. I can't find a good solution to site, I have read tons of great posts on multi tenancy for ASP MVC but I still need some good advice.
I have an ASP MVC Entity Framework 6 Code First web application. This app has to work for many different clients using a single database for all of them.
I have an entity for all the clients, and each client can have different hosts.
public class Client
{
public int ClientId { get; set; }
public string Name { get; set; }
...
public ICollection<ClientHost> Hosts { get; set; }
}
public class ClientHost
{
public int ClientId { get; set; }
public Client Client { get; set; }
public string Name { get; set; }
}
I have added a column "ClientId" to all the entities I need to filter, so I can separate data from different clients.
public class SomeEntity
{
public int Id { get; set; }
...
public int ClientId { get; set; }
}
First thing I need is, base on the host, retrieve the ClientId to work with.
private static int GetClientId()
{
var currentClient = Convert.ToInt32(HttpRuntime.Cache[CacheClient]);
if (currentClient != null) return currentClient;
lock (Synclock)
{
using (var dataContext = new MyDataContext())
{
var urlHost = HttpContext.Current.Request.Url.Host;
currentClient = dataContext.Clients
.FirstOrDefault(p => p.Hosts.Any(h => h.Name == urlHost));
if (currentClient == null) return null;
HttpRuntime.Cache.Insert(CacheClient, currentClient, null, Cache.NoAbsoluteExpiration, TimeSpan.FromSeconds(0), CacheItemPriority.Default, null);
return currentClient;
}
}
}
QUESTION 1
As you see I get the clientId from DB and store it in cache, so I don't have to call DB every time I need it.
I don't know if there is a better approach to get the client Id or, better, to store it.
EDIT
After investigation I have created a variable in DbCOntext and initialize it in the Startup.cs file.
public class MyDataContext : IdentityDbContext<ApplicationUser, CustomRole, int, CustomUserLogin, CustomUserRole, CustomUserClaim>
{
public static string ClientId { get; set; }
public MyDataContext() : base("MyDataBase") { }
public static MyDataContext Create()
{
return new myDataContext();
}
....
}
In Startup.cs
public partial class Startup
{
public void Configuration(IAppBuilder app)
{
MyDataContext.ClientId = ClientConfiguration.GetCurrentClientId();
ConfigureAuth(app);
}
}
QUESTION 2
Once I have the ClientId, I need to add a filter to every query that needs it. Doing this manually can take you to make many errors or forget to do it in some places.
I need a way that the application can add the filter to all queries automatically (only those entities that need it), so I don't have to worry about a client getting other client's data. Also I need to add the ClientId to all the Insert and Update commands.
I have read about filtering and/or use EF Interceptors, but after reading some posts about that I can't figure out how to do it. Need some help here.
Thanks in advance.
EDIT
In order to solve QUESTION 2 I have followed this great post by Xabikos:
http://xabikos.com/2014/11/17/Create-a-multitenant-application-with-Entity-Framework-Code-First-Part-1/
I have changed it a little bit, since I don't use Users to get the current tenant and instead I use the host. This is part of the program I don't know yet how I'm going to solve but, assuming I already have the ClientId I can add filters to all the queries without realizing that is happening:
I have replaced all the user logic:
private static void SetTenantParameterValue(DbCommand command)
{
if (MyDataContext.ClientId == 0) return;
foreach (DbParameter param in command.Parameters)
{
if (param.ParameterName != TenantAwareAttribute.TenantIdFilterParameterName)
continue;
param.Value = MyDataContext.ClientId;
}
}
Same in all the places...
Than I only have to mark the entities that have to filter with TenantAware, indicating the property. In this case I do in my base class and then apply that base class to all the entities I need.
[TenantAware("ClientId")]
public abstract class ClientEntity : Entity, IClientEntity
{
public int ClientId { get; set; }
public Client Client { get; set; }
}
Here are a couple of things I have done in the past that might help.
Question 1:
I am not a big fan of session as the web is supposed to be stateless. However, it is sometimes necessary. Your approach is reasonable. You could also use cookies as well. What I use are Json Web Tokens (JWT) via my authentication provider (Auth0.com). For each request as it is authenticated, I look for this client id. Here is an example. This is MVC 6 as well. You could do the same type of things w/ cookies.
public class Auth0ClaimsTransformer : IClaimsTransformer
{
private string _accountId = AdminClaimType.AccountId.DefaultValue;
private string _clientId = AdminClaimType.ClientId.DefaultValue;
private string _isActive = AdminClaimType.IsActive.DefaultValue;
public Task<ClaimsPrincipal> TransformAsync(ClaimsTransformationContext context)
{
foreach (var claim in context.Principal.Claims)
{
switch (claim.Type)
{
case "accountId":
_accountId = claim.Value ?? _accountId;
break;
case "clientId":
_clientId = claim.Value ?? _clientId;
break;
case "isActive":
_isActive = claim.Value ?? _isActive;
break;
}
}
((ClaimsIdentity)context.Principal.Identity)
.AddClaims(new Claim[]
{
new Claim(AdminClaimType.AccountId.DisplayName, _accountId),
new Claim(AdminClaimType.ClientId.DisplayName, _clientId),
new Claim(AdminClaimType.IsActive.DisplayName, _isActive)
});
return Task.FromResult(context.Principal);
}
Then in my Startup.cs Configure method I plug in my claims transformer.
app.UseJwtBearerAuthentication(options);
app.UseClaimsTransformation(new ClaimsTransformationOptions
{
Transformer = new Auth0ClaimsTransformer()
});
Next I use a base authentication controller that parses out my claims into properties I can use in my controller.
[Authorize]
[Route("api/admin/[controller]")]
public class BaseAdminController : Controller
{
private long _accountId;
private long _clientId;
private bool _isActive;
protected long AccountId
{
get
{
var claim = GetClaim(AdminClaimType.AccountId);
if (claim == null)
return 0;
long.TryParse(claim.Value, out _accountId);
return _accountId;
}
}
public long ClientId
{
get
{
var claim = GetClaim(AdminClaimType.ClientId);
if (claim == null)
return 0;
long.TryParse(claim.Value, out _clientId);
return _clientId;
}
}
public bool IsActive
{
get
{
var claim = GetClaim(AdminClaimType.IsActive);
if (claim == null)
return false;
bool.TryParse(claim.Value, out _isActive);
return _isActive;
}
}
public string Auth0UserId
{
get
{
var claim = User.Claims.FirstOrDefault(x => x.Type == ClaimTypes.NameIdentifier);
return claim == null ? string.Empty : claim.Value;
}
}
private Claim GetClaim(AdminClaimType claim)
{
return User.Claims.FirstOrDefault(x => x.Type == claim.DisplayName);
}
Finally in my controller it is trivial to extract which tenant is making the call. e.g.
public FooController : BaseController
{
public async Task<IActionResult> Get(int id)
{
var foo = await _fooService.GetMultiTenantFoo(ClientId, id);
return Ok(foo);
}
}
Question 2:
One of the ways I have used in the past is create a BaseMultiTenant class.
public class BaseMultiTenant
{
public int ClientId {get;set;}
public virtual Client Client {get;set;}//if you are using EF
}
public class ClientHost : BaseMultiTenant
{
public string Name {get;set;}
//etc
}
Then simply create an extension method for multi-tenant based entities. I know this doesn't "do it automatically" but it is an easy way to ensure each multi-tenant entity is being called only by its owner.
public static IQueryable<T> WhereMultiTenant<T>(this IQueryable<T> entity, int clientId, Expression<Func<T, bool>> predicate)
where T : BaseMultiTenant
{
return entity.Where(x => x.ClientId == clientId)
.Where(predicate);
}
Then when someone calls for their resource you can:
var clientHost = _myContext.ClientHosts
.WhereMultiTenant(ClientId,
x => x.Name == "foo")
.FirstOrDefault();
Hope this is helpful.
Also found a similar example using an interface.

MVC 5 how to specify constraints using attribute routing

I'm trying to do full attribute routing, without conventional routing, and trying to tell my routes which area they belong to based on the current domain.
In convention routing, I can specify my object constraints with this as an example:
context.MapRoute(
"MyRouteName",
"admin/sign-in",
new { controller="AdminController", action="SignIn" },
new { SitePermitted = new SiteConstraint("Admin") } // <-- how do I do this exact line of code but in attribute routing
);
Where SiteConstraint inherits from IRouteConstraint. How do I do the same thing, but using attribute routing? I am looking for something like this:
[AreaName("Admin")]
[Route("admin/sign-in")]
[new SiteConstraint("Admin")]
public ActionResult SignIn(...) {...}
Where MyConstraint has a Match method that gets the current http request and if its domain is "myadmindomain.com", then Match method returns true, and MVC executes this route given that the user is on myadmindomain.com/admin/sign-in.
What you want to do requires the use of the class RouteFactoryAttribute, in MVC 5 inhering from that class you can use your SiteConstraint but using attribute routing. So you can have something like:
public class SiteRouteAttribute : RouteFactoryAttribute
{
public SiteRouteAttribute (string template, string sitePermitted) : base(template)
{
SitePermitted = sitePermitted;
}
public override RouteValueDictionary Constraints
{
get
{
var constraints = new RouteValueDictionary();
constraints.Add("site", new SiteConstraint(SitePermitted));
return constraints;
}
}
public string SitePermitted
{
get;
private set;
}
}
Then in your controller you can have:
[SiteRoute("somepath/{somevariable}/{action=Index}", "Admin")]
public class MyController : Controller
{
public ActionResult Index()
{
....
}
}
Take a look at Jon Galloway's post which has a workable example

Using Autofac for WebForms and n-tier architecture

I am introducing unit testing to an existing webforms application. I am using Moq and Autofac. I'm trying to keep code changes to a minimum, but need to implement the unit testing.
So I have a presentation assembly, a business layer, and a data access layer. My business layer looks something like this:
public class EmployeeBL
{
public Employee SelectById(int id)
{
return new EmployeeDA().SelectById(id);
}
}
My Data access looks something like this:
public class EmployeeDA
{
// unitOfWork defined in the constructor
public Employee SelectById(int id)
{
return unitOfWork.Employees.Where(e => e.id == id);
}
}
Autofac's webforms assembly allows you to declare a public property and specify web.config settings to automatically inject a dependency via HttpModule. To test the EmployeeBL, I made the changes to web.config and declared EmployeeDA as a property:
// Modified for testing
[InjectProperties]
public class EmployeeBL
{
// public property to allow Autofac property injection
public IEmployeeDA EmployeeDA { get; set; }
public Employee SelectById(int id)
{
return EmployeeDA.SelectById(id);
}
}
This works for running the webforms application, but the problem is I need to unit test the business layer independently of a webforms instance. I would like to keep the [InjectProperties] attribute on my EmployeeBL, but I can't tell by the documentation how I can get the EmployeeDA to have its properties injected.
public class EmployeeBLTest
{
private static IContainer { get; set; }
[TestInitialize]
public void Initialize()
{
var builder = new ContainerBuilder();
// Use my fake employeeDA for testing
builder.RegisterType<FakeEmployeeDA>().As<IEmployeeDA>();
// Something magical happens
}
[TestMethod]
public void SelectByIdTest(int id) { /* testing stuff */ }
}
What about:
builder.RegisterType<EmployeeDA>().AsSelf().PropertiesAutowired();
see here:
http://code.google.com/p/autofac/wiki/PropertyInjection