The optimal way to decouple business logic from controller - asp.net

The rule is that controllers shouldn't have business logic, instead they should delegate it to the services. But when we do that, we can't handle all possible cases and return appropriate HTTP response.
Let's look at an example. Let's say that we are building some kind of a social network, and we need to create an endpoint for rating (liking or disliking) a post.
First let's take a look at an example where we delegate the logic to the service, this is our controller action:
public IActionResult Rate(long postId, RatingType ratingType)
{
var user = GetCurrentUser();
PostRating newPostRating = _postsService.Rate(postId, ratingType, user);
return Created(newPostRating);
}
Do you see a problem in this? What if there is no post with the given id, how would we return a not found response? What if user has no permissions to rate a post, how would we return a forbidden response?
PostsService.Rate can only return a new PostRating, but what about other cases? Well, we could throw an exception, we would need to create a lot of custom exception, so that we can map them to the appropriate HTTP responses. I don't like to use exceptions for this, I think there is a better way to do handle these cases instead of exceptions. Because I think that cases when post doesn't exist and when user has no permissions aren't exceptional at all, they're just normal cases just like rating a post successfully.
What I propose, is handling that logic in a controller instead. Because in my opinion, that should be a controllers responsibility anyway, to check all of the permissions before commiting an action. So this is how I would do it:
public IActionResult Rate(long postId, RatingType ratingType)
{
var user = GetCurrentUser();
var post = _postsRepository.GetByIdWithRatings(postId);
if (post == null)
return NotFound();
if (!_permissionService.CanRate(user, post))
return Forbidden();
PostRating newPostRating = new PostRating
{
Post = post,
Author = user,
Type = ratingType
};
_postRatingsRepository.Save(newPostRating);
return Created(newPostRating);
}
This is the way it should be done in my opinion but I bet that someone would say that this is too much logic for the controller, or that you shouldn't use a repository in it.
If you don't like using a repository in controller than where instead would you put a method that gets or saves posts? In service? So there would be PostsService.GetByIdWithRatings and PostsService.Save that would do nothing else but just call PostsRepository.GetByIdWithRatings and PostsRepository.Save. This so unnecessary and only causes boilerplate code.
Update:
Maybe someone will say to check the permissions using PostsService and then call PostsService.Rate. This is bad because it involves more unnecessary trips to database. For an example, it would probably be something like this:
public IActionResult Rate(long postId, RatingType ratingType)
{
var user = GetCurrentUser();
if(_postsService.Exists(postId))
return NotFound();
if(!_postsService.CanUserRate(user, postId))
return Forbidden();
PostRating newPostRating = _postsService.Rate(postId, ratingType, user);
return Created(newPostRating);
}
Do I even need to explain any further why this is bad?

There's a number of ways to handle this, but the closest thing to a "best practice" method is probably using a result class. For example, if your service method creates a rating and then returns that rating it created, you instead return an object that encapsulates the rating along with other relevant information, such as success status, error messages, if any etc.
public class RateResult
{
public bool Succeeded { get; internal set; }
public PostRating PostRating { get; internal set; }
public string[] Errors { get; internal set; }
}
Then, your controller code would become something like:
public IActionResult Rate(long postId, RatingType ratingType)
{
var user = GetCurrentUser();
var result = _postsService.Rate(postId, ratingType, user);
if (result.Succeeded)
{
return Created(result.PostRating);
}
else
{
// handle errors
}
}

What I did (just now) is created new class ApiResult
public class ApiResult
{
public int StatusCode { get; private set; } = 200;
public string RouteName { get; private set; }
public object RouteValues { get; private set; }
public object Content { get; private set; }
public void Ok(object content = null)
{
this.StatusCode = 200;
this.Content = content;
}
public void Created(string routeName, object routeValues, object content)
{
this.StatusCode = 201;
this.RouteName = routeName;
this.RouteValues = routeValues;
this.Content = content;
}
public void BadRequest(object content = null)
{
this.StatusCode = 400;
this.Content = content;
}
public void NotFound(object content = null)
{
this.StatusCode = 404;
this.Content = content;
}
public void InternalServerError(object content = null)
{
this.StatusCode = 500;
this.Content = content;
}
}
And a controller base class with a single method TranslateApiResult
public abstract class CommonControllerBase : ControllerBase
{
protected IActionResult TranslateApiResult(ApiResult result)
{
if (result.StatusCode == 201)
{
return CreatedAtAction(result.RouteName, result.RouteValues, result.Content);
}
else
{
return StatusCode(result.StatusCode, result.Content);
}
}
}
And now in controller I do:
[ApiController]
[Route("[controller]/[action]")]
public class MyController : CommonControllerBase
{
private readonly IMyApiServcie _service;
public MyController (
IMyApiServcie service)
{
_service = service;
}
[HttpGet]
public async Task<IActionResult> GetData()
{
return TranslateApiResult(await _service.GetData());
}
}
In your services you inject repositories and other dependencies:
public class MyApiServcie : IMyApiServcie
{
public async Task<ApiResult> GetData()
{
var result = new ApiResult();
// do something here
result.Ok("success");
return result;
}
}
Now, the reason for Api prefix before the Service is that this service is not meant to be the final service containing all logic.
At this point I would split the business logic into different domains so the services (or facades) would end up without Api prefix in them just to differentiate between i.e. CarService. Preferably these services will not know of anything related to API responses, statuses etc. It's up to you how implement it, though.

Related

Masstransit: How to build a cache filter in the Mediator

We used the Masstransit Mediator to write request/response "Consumers" called from API controllers. Before the consumer is taken action, some ConsumeFilters take place: Logging, Validation and DBTransaction. Next I would like to implement a Cache Filter using simple Microsoft In-Memory Cache. The filter should check if the request object is already in cache, if not the consumer pipe is called and the cache object is added, else the cached object should be returned immediatly.
I could not figure out how write such a filter. Do I need two filters? If I call RespondAsync from ConsumeContext how can a use a generic response type?
Has someone done it before, or should I do I directly in consumer?
Seems like something that should be in the consumer itself. The cache itself could be a dependency of the consumer, which is a single instance and injected into the consumer via the constructor. That way, it would be able to check if the results are in the cache before calling the backing service with the request detail.
Hiding that in a filter seems a little specific to the message type, so within the consumer will likely be easier for developers to understand later.
I figured out a solution to integrate in-memory caching as mass transit scope filter. Currently it is only used in a mediator. Some prequists:
All messages are records (with value bases equal methods)
The query request object inherits from ApplicationQueryRequest (a record)
The query response objects inherits from ApplicationResponse (a record)
The request object has an attribute named Cached Attribute:
[AttributeUsage(AttributeTargets.Class)]
public sealed class CacheAttribute : Attribute
{
public CacheAttribute(int slidingExpireSecs = 30, int absoluteExpireSecs = 100)
{
SlidingExpireSecs = slidingExpireSecs;
AbsoluteExpireSecs = absoluteExpireSecs;
}
public int SlidingExpireSecs { get; }
public int AbsoluteExpireSecs { get; }
}
Therefore each request object can have a cache attribute like:
[Cache]
public record FooRequest
{
}
Target is that the filter automatically fetches data from the cache and stores data in it.
First initialize the mediator with all consumer and send fiters, in our case only one scope filter exists but must be added for send and consume:
services.AddMediator(
configurator =>
{
(context, cfg) =>
{
cfg.UseSendFilter(typeof(CacheScopeFilter<>), context);
cfg.UseConsumeFilter(typeof(CacheScopeFilter<>), context);
Additionally the ICacheScope must be of
services.AddScoped...
THe scope filter looks like this:
public class CacheScopeFilter<T> :
IFilter<SendContext<T>>,
IFilter<ConsumeContext<T>> where T : class
{
private readonly ILogger<T> logger;
private readonly IMemoryCache memoryCache;
private readonly ICacheScope cacheScope;
private CacheOptions cacheOptions;
public CacheScopeFilter(ILogger<T> logger, IOptionsMonitor<CacheOptions> options, IMemoryCache memoryCache, ICacheScope cacheScope)
{
this.logger = logger;
cacheOptions = options.CurrentValue;
options.OnChange(
opts =>
{
logger.LogInformation($"Set Memory Cache enabled: {opts.EnableMemoryCache}");
cacheOptions = opts;
});
this.memoryCache = memoryCache;
this.cacheScope = cacheScope;
}
public async Task Send(ConsumeContext<T> context, IPipe<ConsumeContext<T>> next)
{
var requestName = typeof(T).Name;
logger.LogInformation($"----- Start check cache application query request {requestName} {context.Message}");
cacheScope.RequestKey = null;
if (context.TryGetMessage<ApplicationQueryRequest>(out var requestContext))
{
if(!cacheOptions.EnableMemoryCache)
{
logger.LogInformation("Cache is disabled");
await next.Send(context);
return;
}
var cacheAttribute = (CacheAttribute)Attribute.GetCustomAttribute(
requestContext.Message.GetType(),
typeof(CacheAttribute));
if (cacheAttribute == null)
{
await next.Send(context);
return;
}
cacheScope.RequestKey = typeof(T).FullName + ";" + JsonConvert.SerializeObject(context.Message);
cacheScope.SlidingExpireSecs = cacheAttribute.SlidingExpireSecs;
cacheScope.AbsoluteExpireSecs = cacheAttribute.AbsoluteExpireSecs;
if (memoryCache.TryGetValue(cacheScope.RequestKey, out ApplicationResponse cacheResponse))
{
logger.LogInformation($"Take data from cache {requestName} {context.Message}, CacheKey: {cacheScope.RequestKey}");
await context.RespondAsync(cacheResponse);
return;
}
logger.LogInformation($"Data not in cache, fetching data {requestName} {context.Message}");
}
await next.Send(context);
logger.LogInformation($"----- Finish check cache application query request {requestName} {context.Message}");
}
public async Task Send(SendContext<T> context, IPipe<SendContext<T>> next)
{
var requestName = typeof(T).Name;
logger.LogInformation($"----- Start handling cache application query response {requestName} {context.Message}");
var isCachedSet = context.TryGetPayload<CacheDoneMarker>(out _);
if (context.Message is ApplicationResponse && (cacheScope.RequestKey != null) && !isCachedSet)
{
logger.LogInformation($"Cache data {requestName} {context.Message}, CacheKey: {cacheScope.RequestKey}");
var cacheEntryOptions = new MemoryCacheEntryOptions().
SetSlidingExpiration(TimeSpan.FromSeconds(cacheScope.SlidingExpireSecs)).
SetAbsoluteExpiration(TimeSpan.FromSeconds(cacheScope.AbsoluteExpireSecs));
memoryCache.Set(cacheScope.RequestKey, context.Message, cacheEntryOptions);
context.GetOrAddPayload(() => new CacheDoneMarker());
}
await next.Send(context);
logger.LogInformation($"----- Finish handling cache application query response {requestName} {context.Message}");
}
public void Probe(ProbeContext context)
{
context.CreateFilterScope("cache");
}
}
public class CacheScope : ICacheScope
{
public string RequestKey { get; set; }
public int SlidingExpireSecs { get; set; }
public int AbsoluteExpireSecs { get; set; }
}
// Scope injected !
public interface ICacheScope
{
public string RequestKey { get; set; }
public int SlidingExpireSecs { get; set; }
public int AbsoluteExpireSecs { get; set; }
}
This should also work for distributed cache, but not added yet.

Asp.Net IdentityUser add custom List Property

I am fairly new to coding with asp.net so there might be an obvious answere to my question but I haven't found one yet.
So currently I am developing a site for project management and I want the users to get notified when an event happens, eg. they were added to a new project, a project has been updated etc.
For that I have expanded the IdentityUser Model with a new property List
public class CojectUser : IdentityUser
{
public List<Notification> Notifications { get; set; }
}
public class Notification
{
public int NotificationID { get; set; }
public string Message { get; set; }
public bool Seen { get; set; }
}
When an event happens I add them to the user's notification list and update the user via the userManager.
public class EventBroker<T> : IEventBroker<T>
{
private readonly UserManager<CojectUser> userManager;
public EventBroker(UserManager<CojectUser> userMgr, IUserValidator<CojectUser> userValid)
{
userManager = userMgr;
}
public async Task NotifyAsync(Message<T> message, List<UserRole> recipients)
{
foreach (var user in recipients)
{
var cojectUser = await userManager.FindByNameAsync(user.Name);
if (cojectUser != null)
{
if (cojectUser.Notifications == null)
{
cojectUser.Notifications = new List<Notification>();
}
cojectUser.Notifications.Add(new Notification
{
Message = message.Information,
Seen = false
});
IdentityResult result = await userManager.UpdateAsync(cojectUser);
if (!result.Succeeded)
{
throw new UserUpdateFailException();
}
}
}
}
}
}
I am able to save the custom data to the database, but I am unable to load it again from database.
When I want to display the user's notifications userManager retrieves an user object with null as notification list. Even though the data is stored in database.
public async Task<IActionResult> Index()
{
CojectUser user = await userManager.GetUserAsync(User);
if(user.Notifications == null)
{
user.Notifications = new List<Notification>();
}
return View(user);
}
Data in database:
Can anybody tell me what I am doing wrong?
UserManager don't eager load properties by default.
You should use DatabaseContext directly.
var user = _context.Users.Include(c => c.Notifications).Where(u => u.Id == user.Id).ToList();

Combine [FromBody] with [FromHeader] in WebAPI in .net Core 3.0

we are writing some API which required sessionId in header and some other data in body.
Is it possible to have only one class automatically parsed partially from header and from body?
Something like:
[HttpGet("messages")]
[Produces("application/json")]
[Consumes("application/json")]
[Authorize(Policy = nameof(SessionHeaderKeyHandler))]
public async Task<ActionResult<MessageData>> GetPendingClockInMessages(PendingMessagesData pendingMessagesRequest)
{
some body...
}
with request class like:
public class PendingMessagesData
{
[FromHeader]
public string SessionId { get; set; }
[FromBody]
public string OrderBy { get; set; }
}
I know, it is possible to do this, but it means, that I have to pass SessionId into the other methods as a parameter, instead of pass only one object. And we would have to do that in every API call.
public async Task<ActionResult<MessageData>> GetPendingClockInMessages(
[FromHeader] string sessionId,
[FromBody] PendingMessagesData pendingMessagesRequest)
{
some body...
}
Thank you,
Jakub
we are writing some API which required sessionId in header and some other data in body. Is it possible to have only one class automatically parsed partially from header and from body
Your GetPendingClockInMessages is annotated with a [HttpGet("messages")]. However, a HTTP GET method has no body at all. Also, it can't consume application/json. Please change it to HttpPost("messages")
Typically, SessionId is not passed in header of Session: {SessionId} like other HTTP headers. Session are encrypted via IDataProtector. In other words, you can't get it by Request.Headers["SessionId"].
Apart from the above two facts, you can create a custom model binder to do that.
Since the Session doesn't come from header directly, let's create a custom [FromSession] attribute to replace your [FromHeader]
public class FromSessionAttribute : Attribute, IBindingSourceMetadata
{
public static readonly BindingSource Instance = new BindingSource("FromSession", "FromSession Binding Source", true, true);
public BindingSource BindingSource { get { return FromSessionAttribute.Instance; } }
}
And since you're consuming application/json, let's create a binder as below:
public class MyModelBinder : IModelBinder
{
private readonly JsonOptions jsonOptions;
public MyModelBinder(IOptions<JsonOptions> jsonOptions)
{
this.jsonOptions = jsonOptions.Value;
}
public async Task BindModelAsync(ModelBindingContext bindingContext)
{
var type = bindingContext.ModelType;
var pis = type.GetProperties();
var result= Activator.CreateInstance(type);
var body= bindingContext.ActionContext.HttpContext.Request.Body;
var stream = new System.IO.StreamReader(body);
var json = await stream.ReadToEndAsync();
try{
result = JsonSerializer.Deserialize(json, type, this.jsonOptions.JsonSerializerOptions);
} catch(Exception){
// in case we want to pass string directly. if you don't need this feature, remove this branch
if(pis.Count()==2){
var prop = pis
.Where(pi => pi.PropertyType == typeof(string) )
.Where(pi => !pi.GetCustomAttributesData().Any(ca => ca.AttributeType == typeof(FromSessionAttribute)))
.FirstOrDefault();
if(prop != null){
prop.SetValue( result ,json.Trim('"'));
}
} else{
bindingContext.ModelState.AddModelError("", $"cannot deserialize from body");
return;
}
}
var sessionId = bindingContext.HttpContext.Session.Id;
if (string.IsNullOrEmpty(sessionId)) {
bindingContext.ModelState.AddModelError("sessionId", $"cannot get SessionId From Session");
return;
} else {
var props = pis.Where(pi => {
var attributes = pi.GetCustomAttributesData();
return attributes.Any( ca => ca.AttributeType == typeof(FromSessionAttribute));
});
foreach(var prop in props) {
prop.SetValue(result, sessionId);
}
bindingContext.Result = ModelBindingResult.Success(result);
}
}
}
How to use
Decorate the property with a FromSession to indicate that we want to get the property via HttpContext.Sessino.Id:
public class PendingMessagesData
{
[FromBody]
public string OrderBy { get; set; } // or a complex model: `public MySub Sub{ get; set; }`
[FromSession]
public string SessionId { get; set; }
}
Finally, add a modelbinder on the action method parameter:
[HttpPost("messages")]
[Produces("application/json")]
[Consumes("application/json")]
public async Task<ActionResult> GetPendingClockInMessages([ModelBinder(typeof(MyModelBinder))]PendingMessagesData pendingMessagesRequest)
{
return Json(pendingMessagesRequest);
}
Personally, I would prefer another way, i.e, creating a FromSessionBinderProvider so that I can implement this without too much effort. :
public class FromSessionDataModelBinder : IModelBinder
{
public Task BindModelAsync(ModelBindingContext bindingContext)
{
var sessionId = bindingContext.HttpContext.Session.Id;
if (string.IsNullOrEmpty(sessionId)) {
bindingContext.ModelState.AddModelError(sessionId, $"cannot get SessionId From Session");
} else {
bindingContext.Result = ModelBindingResult.Success(sessionId);
}
return Task.CompletedTask;
}
}
public class FromSessionBinderProvider : IModelBinderProvider
{
public IModelBinder GetBinder(ModelBinderProviderContext context)
{
if (context == null) { throw new ArgumentNullException(nameof(context)); }
var hasFromSessionAttribute = context.BindingInfo?.BindingSource == FromSessionAttribute.Instance;
return hasFromSessionAttribute ?
new BinderTypeModelBinder(typeof(FromSessionDataModelBinder)) :
null;
}
}
(if you're able to remove the [ApiController] attribute, this way is more easier).

a request level singleton object in asp.net

I trying to write a kind of pseudo singleton implementation. I want it to work similar to how HttpContext does work, where I can get an instance to the context doing something as simple as:
var ctx = HttpContext.Current;
So my implementation goes something like this:
public class AppUser
{
public string Username { get; set; }
public string[] Roles { get; set; }
public AppUser()
{
var appuser = HttpContext.Session["AppUser"] as AppUser;
if(appuser == null)
throw new Exception("User session has expired");
Username = appuser.Username;
Roles = appuser.Roles;
}
}
public class WebAppContext
{
const string ContextKey = "WebAppContext";
WebAppContext() { } //empty constructor
public static WebAppContext Current
{
get
{
var ctx = HttpContext.Current.Items[ContextKey] as WebAppContext;
if(ctx == null)
{
try
{
ctx = new WebAppContext() { User = new AppUser() };
}
catch
{
//Redirect for login
}
HttpContext.Current.Items.Add(ContextKey, ctx);
}
return ctx;
}
}
public AppUser User { get; set; }
}
And I try to consume this object as follows:
var appuser = WebAppContext.Current.User;
Now does the above line guarantee I get the user associated with the correct request context; not some other user which is associated with another concurrent http request being processed?
Apart from the fact that I can't understand why would you need to barely copy the user information from the Session container to the Items container, the answer to your question should be - yes, if the Session data is correct then the same data will be available from your static property.
I wrote a blog entry on that once
http://netpl.blogspot.com/2010/12/container-based-pseudosingletons-in.html

How do I test response data using nUnit?

Django has a very handy test client/dummy web browser that one can use in test cases to verify the correctness of HTTP responses (e.g., status codes, context/model data). It does not require you to have the web server running, as it deals directly with the framework to simulate the calls.
I'd really love an nUnit (or similar) equivalent that we can slip right into our test suites. We're working in MVC3 and 4, and want to check things like successful 301 redirects, that model validation is correct, and that ViewModel data is correct in the views.
What's the best solution for this?
ViewModel Data should be easy to check with the following:
public T GetViewModelFromResult<T>(ActionResult result) where T : class
{
Assert.IsInstanceOf<ViewResult>(result);
var model = ((ViewResult)result).Model;
Assert.IsInstanceOf<T>(model);
return model as T;
}
[Test]
public void TheModelHasTheOrder()
{
var controller = new MyController();
var result = controller.MyActionMethod();
var model = GetViewModelFromResult<MyModel>();
Assert.That(model, Is.SameAs(???));
}
As for the model validation, if you are using the out of the box .net property attributes like [Required] etc, you can be pretty sure they will work fine, and won't need testing.
To explicitly test the [Required] etc attributes on your object you will have extract the built in .net validation into another class. Then use that class in your controllers to validate your objects, instead of the Model.IsValid property on your controller.
The model validator class:
public class ModelValidator : IModelValidator
{
public bool IsValid(object entity)
{
return Validate(entity, new List<ValidationResult>());
}
public IEnumerable<ValidationResult> Validate(object entity)
{
var validationResults = new List<ValidationResult>();
Validate(entity, validationResults);
return validationResults;
}
private static bool Validate(object entity, ICollection<ValidationResult> validationResults)
{
if (entity != null)
{
var validationContext = new ValidationContext(entity, null, null);
return Validator.TryValidateObject(entity, validationContext, validationResults);
}
return false;
}
}
This could be verifiable in unit tests with the following:
public class MySampleEntity
{
[Required]
public string X { get; set; }
[Required]
public int Y { get; set; }
}
[TestFixture]
public class ModelValidatorTests
{
[Test]
public void GivenThePropertiesArePopulatedTheModelIsValid()
{
// arrange
var _validator = new ModelValidator();
var _entity = new MySampleEntity { X = "ABC", Y = 50 };
// act
var _result = _validator.IsValid(_entity);
// assert
Assert.That(_result, Is.True);
}
}

Resources