I have 3 action name delete on 3 different controllers which are on "registration,profile and questions" they all have action delete methods. How can I from my registration-delete method call out profile-delete and questions-delete . That way when a user wants to delete their account all they have to do is go on registration-delete instead of going on registration,profile and questions delete methods. I want in 1 [HttpPost, ActionName("Delete")](registration) to call out the other 2 ActionName("Delete") methods as i prefer people to delete everything in one place is this possible? assuming that each user shares a unique ID that is the same all across. The comment in the code im just using to illustrate any help would be great
registration-delete below
[HttpPost, ActionName("Delete")]
public ActionResult DeleteConfirmed()
{
var ss = User.Identity.Name;
var getid = (from s in db.registration where ss == s.email select s.RegistrationID).FirstOrDefault();
registration registration = db.registration.Find(getid);
//This delete's the registration
db.buyers.Remove(registration);
// How can i call-out profile-delete actionname here and questions-delete like
//if (question-delete != null){
// include ActionResult deleteconfirmed("question-delete" }
db.SaveChanges();
return RedirectToAction("logout");
}
If I understand your question, you are asking to call other controller's action method from the currently executing action method? You typically would not do that. The first rule of Controller's action methods is: no action method should be more than 10 lines of code. Basically, an action method is really supposed to be a simple method to collect a view, or call an action in your domain, and return.
In other words, the SRP pattern:
http://codebetter.com/karlseguin/2008/12/05/get-solid-single-responsibility-principle/
Instead, you would organize your Domain-logic (what you describe is considered domain model logic, not controller logic) for this repetitive code such as deleting questions here, but when user is deleted also delete questions, etc.
// an example of IOC injection of your IUserService
private IUserService
public RegistrationController(IUserService userService)
{
_userService = userService;
}
[HttpPost]
public ActionResult Delete()
{
// insert modelstate and/or validation logic
//
if (User.Identity.IsAuthenticated == false)
{
return RedirectToAction("index", "Home");
}
// good practice to never bubble up exceptions to users
try
{
if (_userService.DeleteByEmail(User.Identity.Name) == false)
{
ModalState.AddModelError(String.Empty, "Not allowed?");
return View();
}
// save it all in a single atomic operation here to keep it all in
// a single Transaction Scope that will rollback properly if there is
// an error.
//
db.SaveChanges();
}
catch (Exception)
{
ModalState.AddModelError(String.Empty, "An error occurred...");
return View();
}
// all ok!
return RedirectToAction("logout");
}
Notice how clean this action method is. It just gets down to business with a single line of code or two, and a whole bunch of exit paths to handle the user's experience properly in many different situations.
Now, your domain logic can be encapsulated into a service (or provider, or alike):
namespace MyWebsite.Models
{
public class UserService : IUserService
{
// Todo: convert to some IoC lovin'
//
public Boolean DeleteByEmail(String email)
{
var user =
(from user in db.Registrations
where
user.Email == email
select s).FirstOrDefault();
if (user == null)
return false;
// call into other services to delete them
ProfileDataService.DeleteByUserId(user.UserId);
QuestionsService.DeleteByUserId(user.UserId);
// finally, delete the user
db.Registrations.Delete(user);
// note, I am NOT calling db.SaveChanges() here. Look
// back at the controller method for the atomic operation.
//
return true;
}
}
}
This can be implemented 100s of different ways. The point being is to abstract that logic out to a common code base, or "Domain". I chose to put that logic in your current Website namespace under Models as a shortcut in this example.
As for your other Delete() methods on your other controllers, you would just call into a QuestionsService.DeleteByUserId() and ProfileDataService.DeleteByUserId() to handle those. You can even share those services across the domain, as I showed above.
Related
Situation
We have a controller where users can submit any number of E-Mail addresses to invite other (potential) members as friends. If an address is not found in the database, we send an E-Mail message to that user. Since the user does not has to wait for this process to complete in order to continue working this is done asynchronously.
Sending E-Mails can take a long time if servers respond slowly, are down or overloaded. The E-Mail sender should update the database according to the status received from the E-Mail server, so for example setting the friend request into "Error" state, when a permanent failure occurs, for example if the address does not exists. For this purpose, the E-Mail component implements the function SendImmediateAsync(From,To,Subject,Content,Callback,UserArg). After the message has been delivered (or it failed), the Callback is called with certain arguments about the Delivery state.
When it eventually calls the delegate, the DbContext object has already been disposed (since the controller has been too) and I cannot manually create a new one using new ApplicationDbContext() because there is no constructor that accepts a connection string.
Question
How can I write to the database long after the controller has been disposed? I have not yet figured out how to manually create a DbContext object for myself. An object of type ApplicationDbContext is passed to the constructor of the Controller and I hoped I could instantiate one for myself, but the Constructor has no arguments I can supply (for example connection string). I want to avoid to manually create an SQL Connection and assemble INSERT statements manually and would prefer to work with the entity model we have already set up.
Code
The code shows the affected segment only without any error checking for readability.
[Authorize]
public class MembersController : Controller
{
private ApplicationDbContext _context;
public MembersController(ApplicationDbContext context)
{
_context = context;
}
[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult Friends()
{
MailHandler.SendImmediateAsync(FROM,TO,SUBJECT,CONTENT,
delegate (Guid G, object any)
{
//THIS IS NOT WORKING BECAUSE _context IS DISPOSED
var ctx = _context;
Guid Result = (Guid)any; //user supplied argument
if (G != Guid.Empty)
{
ctx.MailConfirmation.Add(new MailConfirmation()
{
EntryId = Result,
For = EntryFor.FriendRequest,
Id = G
});
if (G == MailHandler.ErrorGuid)
{
var frq = _context.FriendRequest.SingleOrDefault(m => m.Id == Result);
frq.Status = FriendStatus.Error;
ctx.Update(frq);
}
ctx.SaveChanges();
}
}, req.Id);
//rendering view
}
}
First of all, when you are using EF Core with ASP.NET Core's dependency injection, each DbContext instance is scoped per-request, unless you have specified otherwise in ".AddDbContext". This means you should not attempt to re-use an instance of DbContext after that HTTP request has completed. See https://docs.asp.net/en/latest/fundamentals/dependency-injection.html#service-lifetimes-and-registration-options
DbContextOptions, on the other hand, are singletons and can be re-used across requests.
If you need to close the HTTP request and perform an action afterwards, you'll need to create a new DbContext scope an manage it's lifetime.
Second of all, you can overload DbContext's base constructor and pass in DbContextOptions directly. See https://docs.efproject.net/en/latest/miscellaneous/configuring-dbcontext.html
Together, this is what a solution might look like.
public class MembersController : Controller
{
private DbContextOptions<ApplicationDbContext> _options;
public MembersController(DbContextOptions<ApplicationDbContext> options)
{
_options = options;
}
[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult Friends()
{
MailHandler.SendImmediateAsync(FROM,TO,SUBJECT,CONTENT, CreateDelegate(_options) req.Id);
}
private static Action<Guid, object> CreateDelegate(DbContextOptions<ApplicationDbContext> options)
{
return (G, any) =>
{
using (var context = new ApplicationDbContext(options))
{
//do work
context.SaveChanges();
}
};
}
}
public class ApplicationDbContext : DbContext
{
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) : base (options) { }
// the rest of your stuff
}
This assumes, of course, that your "MailHandler" class is properly using concurrency to run the delegate so it doesn't block the thread processing the HTTP request.
Why not just pass the dbContext as the userArgs to your SendImmediateAsync? Then the dbContext will not get disposed and can be passed back when you do the callback. I'm pretty sure that should work.
I have a ASP.NET Web API (.NET 4) application which has a few controllers. We will run several instances of the Web API application on IIS with one difference. Only certain controllers will be available under certain IIS instances. What I was thinking is to disable/unload the controllers that are not applicable to an instance when the instance starts up.
Anyone got some information that could guide me in the right direction on this?
You can put your own custom IHttpControllerActivator in by decorating the DefaultHttpControllerActivator. Inside just check for a setting and only create the controller if allowed.
When you return null from the Create method the user will receive 404 Not Found message.
My example shows a value in App Settings (App.Config or Web.Config) being checked but obviously this could any other environment aware condition.
public class YourCustomControllerActivator : IHttpControllerActivator
{
private readonly IHttpControllerActivator _default = new DefaultHttpControllerActivator();
public YourCustomControllerActivator()
{
}
public IHttpController Create(HttpRequestMessage request, HttpControllerDescriptor controllerDescriptor,
Type controllerType)
{
if (ConfigurationManager.AppSettings["MySetting"] == "Off")
{
//Or get clever and look for attributes on the controller in controllerDescriptor.GetCustomAttributes<>();
//Or use the contoller name controllerDescriptor.ControllerName
//This example uses the type
if (controllerType == typeof (MyController) ||
controllerType == typeof (EtcController))
{
return null;
}
}
return _default.Create(request, controllerDescriptor, controllerType);
}
}
You can switch your activator in like so:
GlobalConfiguration.Configuration.Services.Replace(typeof(IHttpControllerActivator), new YourCustomControllerActivator());
Update
It has been a while since I looked at this question but if I was to tackle it today I would alter the approach slightly and use a custom IHttpControllerSelector. This is called before the activator and makes for a slightly more efficient place to enable and disable controllers... (although the other approach does work). You should be able to decorate or inherit from DefaultHttpControllerSelector.
Rather than unloading the controllers, I think I'd create a custom Authorize attribute that looked at the instance information in deciding to grant authorization.
You would add the following to each controller at the class level, or you could also add this to individual controller actions:
[ControllerAuthorize (AuthorizedUserSources = new[] { "IISInstance1","IISInstance2","..." })]
Here's the code for the Attribute:
public class ControllerAuthorize : AuthorizeAttribute
{
public ControllerAuthorize()
{
UnauthorizedAccessMessage = "You do not have the required access to view this content.";
}
//Property to allow array instead of single string.
private string[] _authorizedSources;
public string UnauthorizedAccessMessage { get; set; }
public string[] AuthorizedSources
{
get { return _authorizedSources ?? new string[0]; }
set { _authorizedSources = value; }
}
// return true if the IIS instance ID matches any of the AllowedSources.
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
if (httpContext == null)
throw new ArgumentNullException("httpContext");
//If no sources are supplied then return true, assuming none means any.
if (!AuthorizedSources.Any())
return true;
return AuthorizedSources.Any(ut => ut == httpContext.ApplicationInstance.Request.ServerVariables["INSTANCE_ID"]);
}
The IHttpControllerActivator implementation doesn't disable the routes defined using attribute routing , if you want to switch on/off a controller and have a default catch all route controller. Switching off using IHttpControllerActivator disables the controller but when the route is requested it doesn't hit the catch all route controller -it simply tries to hit the controller that was removed and returns no controller registered.
Consider, for example's sake, the logic "A user may only edit or delete a comment that the user has authored".
My Controller Actions will repeat the logic of checking whether the currently logged in user can affect the comment. Example
[Authorize]
public ActionResult DeleteComment(int comment_id)
{
var comment = CommentsRepository.getCommentById(comment_id);
if(comment == null)
// Cannot find comment, return bad input
return new HttpStatusCodeResult(400);
if(comment.author != User.Identity.Name)
// User not allowed to delete this comment, return Forbidden
return new HttpStatusCodeResult(403);
// Error checking passed, continue with delete action
return new HttpStatusCodeResult(200);
}
Of course, I can bundle that logic up in a method so that I'm not copy / pasting that snippet; however, taking that code out of the controller and putting it in a ValidationAttribute keeps my Action smaller and easier to write tests for. Example
public class MustBeCommentAuthorAttribute : ValidationAttribute
{
// Import attribute for Dependency Injection
[Import]
ICommentRepository CommentRepository { get; set; }
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
int comment_id = (int)value;
var comment = CommentsRepository.getCommentById(comment_id);
if(comment == null)
return new ValidationResult("No comment with that ID");
if(comment.author != HttpContext.Current.User.Identity.Name)
return new ValidationResult("Cannot edit this comment");
// No errors
return ValidationResult.Success;
}
}
public class DeleteCommentModel
{
[MustBeCommentAuthor]
public int comment_id { get; set; }
}
Is Model Validation the right tool for this job? I like taking that concern out of the controller Action; but in this case, it may complicate things further. This is especially true when you consider that this Action is part of a RESTful API and needs to return a different HTTP Status Code depending on the Validation errors in the ModelState.
Is there "best practice" in this case?
Personally, I think that it looks nice, but you are getting carried away with annotations. I think that this does not belong in your presentation layer and it should be handled by your service layer.
I would have something on the lines of:
[Authorize]
public ActionResult DeleteComment(int comment_id)
{
try
{
var result = CommentsService.GetComment(comment_id, Auth.Username);
// Show success to the user
}
catch(Exception e)
{
// Handle by displaying relevant message to the user
}
}
I'm working on a simple application and am looking to get my head around the CodeFirst EF approach. So far so good.
I have managed to get Create and delete sorted and have got edit working. The thing is I think the edit code could be improved; I'm just not sure how. So here it is:
public ActionResult Edit(int id, CreateResourceViewModel model)
{
if (ModelState.IsValid)
{
// save the changes
//UpdateModel(model.Resource);
//resourceAdminManager.SaveChanges();
Resource current = resourceAdminManager.Resources.Find(id);
current.ResourceTypeID = model.Resource.ResourceTypeID;
current.Name = model.Resource.Name;
current.Description = model.Resource.Description;
current.Email = model.Resource.Email;
current.TurnAroundTime = model.Resource.TurnAroundTime;
resourceAdminManager.SaveChanges();
return RedirectToAction("Index");
}
else
{
return View(model);
}
}
I know there is no exception handling around this, which I need to address but my main concern is the fact that I have manually updated the model. My concerns are:
1. This is in the controller
2. This is hard coded and so any changes to the model will require a re-work of code
Can someone suggest a better way of doing this please.
Many thanks
Nathan
So following on from the AutoMapper suggestions:
This is very helpful and I've started to play with this. I'm running into a little trouble with it.
The contoller now looks like:
if (ModelState.IsValid)
{
try
{
var current = resourceAdminManager.Resources.Find(id);
current = Mapper.Map<CreateResourceViewModel, Resource>(model);
resourceAdminManager.SaveChanges();
return RedirectToAction("Index");
}
catch (Exception exc)
{
ModelState.AddModelError("Error", exc); // or, use a generic error.
}
}
return View(model);
The error occurs in the view when I click save. I get null exception on the following:
<%: Html.DropDownListFor(model => model.Resource.ResourceTypeID, new SelectList(Model.ResourceTypes, "ResourceTypeId", "Title"), "-- Select Resource Type --")%>
Any ideas on what I may be missing here?
One word: AutoMapper.
Will turn those 5 left-to-right boilerplate lines into one.
One of the best MVC (well, C#) add-on's i've ever found.
If implemented correctly (i think your ViewModel may need to change a tad), your action will look like this:
public ActionResult Edit(int id, CreateResourceViewModel model)
{
if (ModelState.IsValid)
{
try
{
var current = resourceAdminManager.Resources.Find(id);
current = Mapper.Map<CreateResourceViewModel,Resource>(model);
resourceAdminManager.SaveChanges();
return RedirectToAction("Index");
}
catch (Exception exc)
{
ModelState.AddModelError("Error", exc); // or, use a generic error.
}
}
return View(model);
}
A warning: this will basically replace the entire entity. So if you only want certain fields changed, only include those in the ViewModel, so the other's will be unchanged. You can also specify "Ignore" in AutoMapper so those fields won't get touched.
And to address your concerns:
This is in the controller
Well actually, it should be. In fact, that's the controllers job - updating the model.
The fact you have boilerplate L-R code is bad, which is why AutoMapper helps.
This is hard coded and so any changes to the model will require a re-work of code
Yup, again - AutoMapper solves this. It works on convention (like MVC), so if you have a proeprty in the ViewModel with the same name as the target model (domain object), you won't have to explicitly map -> it will just work. Furthermore, the mapping is a single static class, so it can be easily maintained.
You are using ViewModel which is very good practise.
1] For Exception handling will suggest to inherit your Controller
from your custom "BaseControllor" instead of System.Web.Mvc.Controller
public class YourController : BaseController
In "BaseControllor" override OnException so that all excptions across your Controllor Actions will be catch in it.
public class BaseController: Controller
{
protected override void OnException(ExceptionContext filterContext)
{
base.OnException(filterContext);
}
}
2] You need to refactor your "Save" code into differnt class in same project or differnt project and differnt Class
3] Yes for any Changes in model you would need to make changes in viewmodel, and in your save logic
I have on post action in controller. Code is as given below
[HttpPost]
public ActionResult Create(Int64 id, FormCollection collection)
{
var data = Helper.CreateEmptyApplicationsModel();
if (TryUpdateModel(data))
{
// TODO: Save data
return RedirectToAction("Edit", "Applications", new { area = "Applications", id = id });
}
else
{
// TODO: update of the model has failed, look at the error and pass back to the view
if (!ModelState.IsValid)
{
if (id != 0) Helper.ShowLeftColumn(data, id);
return View("Create", data);
}
}
return RedirectToAction("Details", "Info", new { area = "Deals", InfoId= id });
}
I wrote test case for this as below
[TestMethod]
public void CreateTest_for_post_data()
{
var collection = GetApplicantDataOnly();
_controller.ValueProvider = collection.ToValueProvider();
var actual = _controller.Create(0, collection);
Assert.IsInstanceOfType(actual, typeof(RedirectToRouteResult));
}
When I debug this single test case, test case passed because condition
if (TryUpdateModel(data)) return true and its goes to if condition.
But when I debug test cases from entire solution this test case failed because it goes to else condition of " if (TryUpdateModel(data))".
I dont know why..
Please help...
Thanks
I've experienced a similar problem which will solve your issue provided you don't need to use a FormCollection.
I haven't used TryUpdateModel ever since the day I learned of the Auto-Binding feature. Auto-binding, in a nutshell pretty much does the work TryUpdateModel would do for you, that is, it'll set a model object according to the values found in the FormCollection as well as attempting to validate the model. And it does this automatically. All you'll have to do is place a parameter in the ActionMethod and it will automatically have it's properties filled with the values found in the FormCollection. Your Action signature will then turn into this:
public ActionResult Create(Int64 id, SomeModel data)
Now you don't need to call TryUpdateModel at all. You still need to check if the ModelState is valid to decide whether or not to redirect or return a view.
[HttpPost]
public ActionResult Create(Int64 id, SomeModel data)
{
if (ModelState.IsValid)
{
// TODO: Save data
return RedirectToAction("Edit", "Applications", new { area = "Applications", id = id });
}
else
{
if (id != 0) Helper.ShowLeftColumn(data, id);
return View("Create", data);
}
}
This won't throw an exception in your unit tests so that's one problem solved. However, there's another problem now. If you run your application using the above code, it'll work just fine. You model will be validated upon entering the Action and the correct ActionResult, either a redirect or a view, will be returned. However, when you try to unit test both paths, you'll find the model will always return the redirect, even when the model is invalid.
The problem is that when unit testing, the model isn't being validated at all. And since the ModelState is by default valid, ModelState.IsValid will always return true in your unit tests and thus will always return a redirect even when the model is invalid.
The solution: call TryValidateModel instead of ModelState.IsValid. This will force your unit test to validate the model. One problem with this approach is that means the model will be validated once in your unit tests and twice in your application. Which means any errors discovered will be recorded twice in your application. Which means if you use the ValidationSummary helper method in your view, your gonna see some duplicated messages listed.
If this is too much to bear, you can clear the ModelState before calling TryValidateModel. There are some problems with doing so because your gonna lose some useful data, such as the attempted value if you clear the ModelState so you could instead just clear the errors recorded in the ModelState. You can do so by digging deep into the ModelState and clearing every error stored in every item like so:
protected void ClearModelStateErrors()
{
foreach (var modelState in ModelState.Values)
modelState.Errors.Clear();
}
I've placed the code in a method so it can be reused by all Actions. I also added the protected keyword to hint that this might be a useful method to place in a BaseController that all your controllers derive from so that they all have access to this method.
Final Solution:
[HttpPost]
public ActionResult Create(Int64 id, SomeModel data)
{
ClearModelStateErrors();
if (ModelState.IsValid)
{
// TODO: Save data
return RedirectToAction("Edit", "Applications", new { area = "Applications", id = id });
}
else
{
if (id != 0) Helper.ShowLeftColumn(data, id);
return View("Create", data);
}
}
NOTE: I realize I didn't shed any light on the root issue. That's because I don't completely understand the root issue. If you notice the failing unit test, it fails because a ArgumentNullException was thrown because the ControllerContext is null and it is passed to a method which throws an exception if the ControllerContext is null. (Curse the MVC team with their damned defensive programming).
If you attempt to mock the ControllerContext, you'll still get an exception, this time a NullReferenceException. Funnily enough, the stack trace for the exception shows that both exceptions occur at the same method, or should I say constructor, located at System.Web.Mvc.ChildActionValueProvider. I don't have a copy of the source code handy so I have no idea what is causing the exception and I've yet to find a better solution than the one I offered above. I personally don't like my solution because I'm changing the way I code my application for the benefit of my unit tests but there doesn't seem to be a better alternative. I bet the real solution will involve mocking some other object but I just don't know what.
Also, before anyone gets any smart ideas, mocking the ValueProvider is not a solution. It'll stop exceptions but your unit tests won't be validating your model and your ModelState will always report that the model is valid even when it isn't.
You might want to clean up your code a bit:
[HttpPost]
public ActionResult Create(int id, FormCollection collection)
{
var data = Helper.CreateEmptyApplicationsModel();
if (!ModelState.IsValid)
{
if (id != 0)
{
Helper.ShowLeftColumn(data, id);
}
return View("Create", data);
}
if (TryUpdateModel(data))
{
return RedirectToAction("Edit", "Applications", new { area = "Applications", id = id });
}
return RedirectToAction("Details", "Info", new { area = "Deals", InfoId= id });
}
Don't use Int64, just use int.
As for your failing test, I would expect your test to fail all the time since TryUpdateModel will return false. As your running the code from a unit test, the controller context for a controller is not available, thus TryUpdateModel will fail.
You need to somehow fake/mock TryUpdateModel so that it does not actually run properly. Instead you "fake" it to return true. Here's some links to help:
How do I Unit Test Actions without Mocking that use UpdateModel?
The above SO answer shows an example using RhinoMocks which is a free mocking framework.
Or this:
http://www.codecapers.com/post/ASPNET-MVC-Unit-Testing-UpdateModel-and-TryUpdateModel.aspx
Debug your tests and check the modelstate errors collection, all the errors that the tryupdatemodel encountered should be there.