I use Service - Repository pattern.
I keep all basic CRUD in all repositories and won't touch/change. Service is empty and it's only for functions that are customized. So when I want to execute few actions, I create and customize like this in service class.
HomeService.cs
public ReceiptModel Save(UserModel user, InvoiceModel invoice)
{
BeginTransaction();
User.Save(user);
var invoiceId = Invoice.Save(invoice);
Receipt.Save(...);
Commit();
var result = Receipt.Get(x => x.InvoiceId == invoiceId);
return result;
}
Now in CQRS + MediatR,
do I need to call _mediator.Send() 4 times?
HomeController.cs
[HttpPost]
public async Task<IResult> InsertInvoice([FromBody]SaveInvoiceCommand invoice)
{
await _mediator.Send(new SaveUserCommand(user));
var invoiceId = await _mediator.Send(invoice);
await _mediator.Send(new SaveReceiptCommand(invoiceId));
var result = _mediator.Send(new GetReceiptQuery(invoiceId));
return Results.Ok(result);
}
or create new handler to combine these 4 actions?
If no.2, doesn't it bloat abit more when I have alot of customized functions comparing with service repository pattern?
Related
I want to have the parameter I receive in the controller from a client be used to authorize my "POST" methods used in my projects(Users can only edit projects they are assigned to). UserIds will live on the project so I need to fetch the project and verify the current user id is in the project.
// I want to modify my policy below where I can use "projectId". ProjectId is dynamic and passed in from a ajax call
[Authorize(Policy = "CanModifyProject")]
[HttpPost]
public async Task<IActionResult> SaveWorker(var projectId, workerModel worker)
{
// Code here....
}
My policy
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, CanModifyProject requirement)
{
bool isSuperAdmin = context.User.IsAdmin();
bool isAdmin = context.User.IsAdmin();
var currentUserId = context.User.GetUserId();
// I NEED TO HAVE THE DYNAMIC PROJECT ID here recieved by "POST" methods
int projectId = 0;
// Check to see if the project has the assigned User
var projectUserIsAssigned = this.projectRepository.ProjectIsAssignedToUser(projectId currentUserId);
if (isSuperAdmin || isAdmin && projectUserIsAssigned)
{
context.Succeed(requirement);
return Task.CompletedTask;
}
else
{
context.Fail();
return Task.CompletedTask;
}
}
How can I access my ProjectId in my post method ???
The authorization phase is fairly early in the request processing pipeline, before executing the action. So no view model is bound yet (by model binding). I would use an IActionFilter or IAsyncActionFilter for this purpose. There you can access the ActionExecutingContext.ActionArguments to get the bound parameter you want for your custom authorization.
Here I would like to try solving it the way you want with the code usually executed in the scope of IAuthorizationFilter or IAsyncAuthorizationFilter (there is another place in which it's executed in the AuthorizationMiddleware). With this approach, it's limited in how you get the action argument value. It should be plainly accessible via some key and the raw value we get is of course a string. As in your specific requirement, it's just a parameter of int which can be sent via form-data or query string from the client.
The point here is we can get such a raw value using a CompositeValueProvider.
The code should say it all:
//inject IOptions<MvcOptions> into your CanModifyProject requirement class
//name it as _mvcOptions
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, CanModifyProject requirement) {
bool isSuperAdmin = context.User.IsAdmin();
bool isAdmin = context.User.IsAdmin();
var currentUserId = context.User.GetUserId();
var projectId = 0;
//get projectId
if(context.Resource is ActionContext ac &&
ac.ActionDescriptor is ControllerActionDescriptor ca)
{
//match only the related controller method
if(ca.ControllerTypeInfo == typeof(YourController) &&
ca.MethodInfo.Name == nameof(YourController.SaveWorker)) {
//create the composite value provider
var valueProvider = await CompositeValueProvider.CreateAsync(ac, _mvcOptions.Value.ValueProviderFactories);
//get the raw id (as string)
var id = valueProvider.GetValue("projectId").FirstValue ?? "";
//parse your projectId here
int.TryParse(id, out projectId);
}
}
//your remaining code (unchanged)
//...
}
I have a method that does some work in a transaction:
public async Task<int> AddAsync(Item item)
{
int result;
using (var transaction = await _context.Database.BeginTransactionAsync())
{
_context.Add(item);
// Save the item so it has an ItemId
result = await _context.SaveChangesAsync();
// perform some actions using that new item's ItemId
_otherRepository.Execute(item.ItemId);
transaction.Commit();
}
return result;
}
I'd like to add unit tests to check that if _context.SaveChangesAsync or _otherRepository.Execute fail then the transaction is rolled back, is that possible?
I can't see a way to do that using InMemory or SQLite?
#Ilya Chumakov's excellent answer allowed me to unit test for the transaction. Our discussion in the comments then exposed some interesting points that I thought were worth moving into an answer so they'd be more permanent and easier to see:
The primary point is that the events logged by Entity Framework change dependent on the database provider, which surprised me. If using the InMemory provider you get just one event:
Id:1; ExecutedCommand
Whereas if you use Sqlite for the in-memory database you get four events:
Id:1; ExecutedCommand
Id:5; BeginningTransaction
Id:1; ExecutedCommand
Id:6; CommittingTransaction
I hadn't expected the events logged to change depending on the DB provider.
To anyone wanting to look into this more, I captured the event details by changing Ilya's logging code as follows:
public class FakeLogger : ILogger
{
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception,
Func<TState, Exception, string> formatter)
{
var record = new LogRecord
{
EventId = eventId.Id,
RelationalEventId = (RelationalEventId) eventId.Id,
Description = formatter(state, exception)
};
Events.Add(record);
}
public List<LogRecord> Events { get; set; } = new List<LogRecord>();
public bool IsEnabled(LogLevel logLevel) => true;
public IDisposable BeginScope<TState>(TState state) => null;
}
public class LogRecord
{
public EventId EventId { get; set; }
public RelationalEventId RelationalEventId { get; set; }
public string Description { get; set; }
}
And then I adjusted my code that returns an in-memory database so that I could switch in-memory DB provider as follows:
public class InMemoryDatabase
{
public FakeLogger EfLogger { get; private set; }
public MyDbContext GetContextWithData(bool useSqlite = false)
{
EfLogger = new FakeLogger();
var factoryMock = Substitute.For<ILoggerFactory>();
factoryMock.CreateLogger(Arg.Any<string>()).Returns(EfLogger);
DbContextOptions<MyDbContext> options;
if (useSqlite)
{
// In-memory database only exists while the connection is open
var connection = new SqliteConnection("DataSource=:memory:");
connection.Open();
options = new DbContextOptionsBuilder<MyDbContext>()
.UseSqlite(connection)
.UseLoggerFactory(factoryMock)
.Options;
}
else
{
options = new DbContextOptionsBuilder<MyDbContext>()
.UseInMemoryDatabase(Guid.NewGuid().ToString())
// don't raise the error warning us that the in memory db doesn't support transactions
.ConfigureWarnings(x => x.Ignore(InMemoryEventId.TransactionIgnoredWarning))
.UseLoggerFactory(factoryMock)
.Options;
}
var ctx = new MyDbContext(options);
if (useSqlite)
{
ctx.Database.EnsureCreated();
}
// code to populate the context with test data
ctx.SaveChanges();
return ctx;
}
}
Finally, in my unit test I made sure to clear the event log just before the assert part of my test to ensure I don't get a false positive due to events that were logged during the arrange part of my test:
public async Task Commits_transaction()
{
using (var context = _inMemoryDatabase.GetContextWithData(useSqlite: true))
{
// Arrange
// code to set up date for test
// make sure none of our setup added the event we are testing for
_inMemoryDatabase.EfLogger.Events.Clear();
// Act
// Call the method that has the transaction;
// Assert
var result = _inMemoryDatabase.EfLogger.Events
.Any(x => x.EventId.Id == (int) RelationalEventId.CommittingTransaction);
You could check EF Core logs for a RelationalEventId.RollingbackTransaction event type. I provided full details here:
How to trace an Entity Framework Core event for integration testing?
How it could look:
Assert.True(eventList.Contains((int)RelationalEventId.CommittingTransaction));
I think you are asking about how to rollback when a commit fails, EF core will auto rollback on if any of the statement failed
Read more here
, if you are asking for other reason or you want to do something when rollback happens, just to add try catch blocks,
using (var transaction = await
_context.Database.BeginTransactionAsync()){
try {
_context.Add(item);
// Save the item so it has an ItemId
result = await _context.SaveChangesAsync();
// perform some actions using that new item's ItemId
_otherRepository.Execute(item.ItemId);
transaction.Commit();
}
catch (Exception)
{
// failed, Do something
} }
I'm just getting to grips with creating a new WebAPI2 project in ASP.NET. I'm trying to get the controller to return data from a DTO I have created rather than the raw object classes that EF created. I've been following a tutorial on Microsoft Docs and have got my method which returns all records to work using the DTO, but I can't figure out how to correctly modify the method which only returns the record with the ID matching the passed parameter using an asynchronous task, like the default method does.
The default method generated by Visual Studio looks like this:
[ResponseType(typeof(Post))]
public async Task<IHttpActionResult> GetPost(int id)
{
Post post = await db.Post.FindAsync(id);
if (post == null)
{
return NotFound();
}
return Ok(post);
}
and I've got my modified method looking like this:
[ResponseType(typeof(PostDTO))]
public async Task<IHttpActionResult> GetPost(int id)
{
var _post = from p in db.Post
where p.PostID == id
select new PostDTO()
{
PostID = p.PostID,
SubmitTime = p.SubmitTime,
SubmitUsername = p.SubmitUsername,
};
if (_post == null)
{
return NotFound();
}
return Ok(_post);
}
This methods works just fine, but as you can see, it doesn't make use of .NET's Await/Async feature to perform the query asynchronously. I'll be honest and admit that I don't actually know if this matters, but I feel like if the default method was asynchronous, so should mine be. I just can't work out where to insert the Async and Await keywords to make this work.
You can use this method as,
[ResponseType(typeof(PostDTO))]
public async Task<IHttpActionResult> GetPost(int id)
{
var _post = await (from p in db.Post
where p.PostID == id
select new PostDTO()
{
PostID = p.PostID,
SubmitTime = p.SubmitTime,
SubmitUsername = p.SubmitUsername,
}).ToListAsync();
if (_post == null)
{
return NotFound();
}
return Ok(_post);
}
From JavaScript client code I am creating the following data:
var employee = {
FirstName: "Rudolf",
Salary: 99
};
I then pass this through an Ajax call to an MVC Web API Controller Action:
using System.Web.Http.OData;
public async Task<IHttpActionResult> Patch([FromUri] int employeeId, [FromBody] Delta<Employee> employee)
{
await _employeeService.Patch(employeeId, employee);
return Ok();
}
This calls my service to update the database as follows:
public async Task Patch(int employeeId, Delta<Employee> employee)
{
using (var context = new DBEntities())
{
if (employee.TryGetPropertyValue("Salary", out object salary))
{
var ss = Convert.ToDouble(salary); // Always 0
}
if (employee.TryGetPropertyValue("FirstName", out object firstName))
{
var ss = Convert.ToString(firstName); // Expected value
}
var currentEmployee = await context.Employees
.FirstOrDefaultAsync(e => e.Id == employeeId);
if (currentEmployee == null)
return;
employee.Patch(currentEmployee);
await context.SaveChangesAsync();
}
}
Note: I missed out some of the details for brevity as the actual client-server call is working fine.
The code seems to work as expected, but the Salary property (the only none-string one) is always set to 0 (zero). So that field never get's updated.
Any ideas why the Salary is not being passed through?
Note: I use very similar client-server code for GET/POST/PUT/DELETE and they all work fine, so I believe it is related to the Delta<> part.
Yes, I encountered the same problem with int properties.
I solved the problem using SimplePatch (v1.0 is only 10KB).
Disclaimer: I'm the author of the project.
It is inspired to Microsoft.AspNet.WebApi.OData but SimplePatch has no dependencies.
How to use
Install the package using:
Install-Package SimplePatch
Your MVC Web API Controller Action becomes:
using SimplePatch;
public async Task<IHttpActionResult> Patch([FromUri] int employeeId, [FromBody] Delta<Employee> employee)
{
await _employeeService.Patch(employeeId, employee);
return Ok();
}
Then your Patch method becomes:
public async Task Patch(int employeeId, Delta<Employee> employee)
{
using (var context = new DBEntities())
{
if (employee.TryGetPropertyValue("Salary", out object salary))
{
var ss = Convert.ToDouble(salary);
}
if (employee.TryGetPropertyValue("FirstName", out object firstName))
{
var ss = Convert.ToString(firstName);
}
var currentEmployee = await context.Employees
.FirstOrDefaultAsync(e => e.Id == employeeId);
if (currentEmployee == null)
return;
employee.Patch(currentEmployee);
await context.SaveChangesAsync();
}
}
Also, SimplePatch gives you the ability to ignore some properties when calling the Patch method.
Global.asax or Startup.cs
DeltaConfig.Init((cfg) =>
{
cfg.ExcludeProperties<Employee>(x => x.YourPropertyName);
});
If Salary has type int then there is an issue with that type
The Json parser converts integers to 64 bit ones, which won't match the 32 bit int properties declared on the entity and thus get ignored by this method.
This problem is mentioned here
So, you can use long instead of int or using the OData media type formatters
See this
I have an webapi2.0 ,through webapi i'm hitting another restful service which is implemented in java.
actually i have only one concerns here how can implement the multi threading through webapi because when i'm hitting the service then some of my static class and other related data are not populating .
can any one help me regarding to the rest services.
Here is my code:
public HttpResponseMessage ListEmployee()
{
Employee emp = new Employee{};
List<Employee> EmployeeList = new List<Employee>{};
var emp = emp.getlist();
foreach(var item in emp)
{
var details = emp.getDetails(item.id);
}
return WebApiResponse.Success<List<APICreatePatientModel>>(EmployeeList);
}
It might help you,
$(document).ready(function () {
// Send an AJAX request
$.getJSON(apiUrl)
.done(function (data) {
// On success, 'data' contains a list of Employee.
$.each(data, function (key, item) {
// TODO as per you!.
});
});
});
For more detail, you can go links
http://www.asp.net/web-api/overview/getting-started-with-aspnet-web-api/tutorial-your-first-web-api
http://www.codeproject.com/Articles/344078/ASP-NET-WebAPI-Getting-Started-with-MVC-and-WebAP
https://docs.asp.net/en/latest/tutorials/first-web-api.html