I'm new to Dapper or even ASP.NET MVC and I'm trying to write what should be a very simple CRUD app. Basically I have two tables User and File. User table stores some details about the User and when you click details it should display some more details and any files that user will upload (there can be none or up to 10).
File table stores the filenames. How do I create a model and then view for something like this using Dapper. It seems simple but I can't seem to figure this out. Can I put both models on my view and then loop through the file model to display the files if any. if so then how?
here is my details controller.
public ActionResult Details(int id)
{
Users user = new Users();
using (IDbConnection db = new SqlConnection(ConfigurationManager.ConnectionStrings["myconn"].ConnectionString))
{
user= db.Query<Users>("SELECT * FROM Users As t LEFT JOIN Files AS f ON t.EID = f.EID WHERE t.EID=" +id, new{ id }).SingleOrDefault() ;
}
return View(trade);
}
First of all try to read more about architecture, learn how to organize your code at the first place. For instance calling database directly from controller is a bad idea. I would do something like this:
Move all database calls to separate repository classes according to responsibilities.
public class UsersRepository
{
public User GetUser(int id)
{
User user = new User();
using (IDbConnection db = new SqlConnection(ConfigurationManager.ConnectionStrings["myconn"].ConnectionString))
{
user= db.Query<User>("SELECT * FROM Users where EID=#id", new{ id }).SingleOrDefault();
}
return user;
}
public List<File> GetUserFiles(int id)
{
List<File> files = new List<File>();
using (IDbConnection db = new SqlConnection(ConfigurationManager.ConnectionStrings["myconn"].ConnectionString))
{
files = db.Query<File>("SELECT * FROM Files where UserID=#id", new{ id });
}
return files;
}
}
Create model to store your data for detail view
public class UserDetails
{
public User User { get; set; }
public List<File> Files { get; set; }
}
Create service or engine to organize you business logic
public class UsersService
{
private UsersRepository _usersRepository;
public UsersService()
{
_usersRepository = new UsersRepository();
}
public UserDetails GetUserDetails(int userId)
{
var details = new UserDetails();
details.User = _usersRepository.GetUser(id);
details.Files = _usersRepository.GetUserFiles(id);
return details;
}
}
Call it in controller
public class UsersController : Controller // or whatever the base class you have
{
private UsersService _usersService;
public UsersController()
{
_usersService = new UsersService();
}
public ActionResult Details(int id)
{
var details = _usersService.GetUserDetails(id);
return View(details);
}
}
But keep in mind that its very simplified example. For instance it is better to use dependency injection rather than initialize services or repositories in constructor. Also in many cases it is not very smart to use same classes for view and repository logic, consider using DTO and domain classes insead. For simple apps and demonstraion it should be ok though.
I want to reach the Bikes controller with these URL's:
/bikes // (default path for US)
/ca/bikes // (path for Canada)
One way of achieving that is using multiple Route Attributes per Action:
[Route("bikes")]
[Route("{country}/bikes")]
public ActionResult Index()
To keep it DRY I'd prefer to use a RoutePrefix, but multiple Route Prefixes are not allowed:
[RoutePrefix("bikes")]
[RoutePrefix("{country}/bikes")] // <-- Error: Duplicate 'RoutePrefix' attribute
public class BikesController : BaseController
[Route("")]
public ActionResult Index()
I've tried using just this Route Prefix:
[RoutePrefix("{country}/bikes")]
public class BikesController : BaseController
Result: /ca/bikes works, /bikes 404s.
I've tried making country optional:
[RoutePrefix("{country?}/bikes")]
public class BikesController : BaseController
Same result: /ca/bikes works, /bikes 404s.
I've tried giving country a default value:
[RoutePrefix("{country=us}/bikes")]
public class BikesController : BaseController
Same result: /ca/bikes works, /bikes 404s.
Is there another way to achieve my objective using Attribute Routing?
(And yes, I know I can do this stuff by registering routes in RouteConfig.cs, but that's what not I'm looking for here).
I'm using Microsoft.AspNet.Mvc 5.2.2.
FYI: these are simplified examples - the actual code has an IRouteConstraint for the {country} values, like:
[Route("{country:countrycode}/bikes")]
I am a bit late to the party, but i have a working solution for this problem. Please find my detailed blog post on this issue here
I am writing down summary below
You need to create 2 files as given below
_3bTechTalkMultiplePrefixDirectRouteProvider.cs
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Web.Http.Controllers;
using System.Web.Http.Routing;
namespace _3bTechTalk.MultipleRoutePrefixAttributes {
public class _3bTechTalkMultiplePrefixDirectRouteProvider: DefaultDirectRouteProvider {
protected override IReadOnlyList GetActionDirectRoutes(HttpActionDescriptor actionDescriptor, IReadOnlyList factories, IInlineConstraintResolver constraintResolver) {
return CreateRouteEntries(GetRoutePrefixes(actionDescriptor.ControllerDescriptor), factories, new [] {
actionDescriptor
}, constraintResolver, true);
}
protected override IReadOnlyList GetControllerDirectRoutes(HttpControllerDescriptor controllerDescriptor, IReadOnlyList actionDescriptors, IReadOnlyList factories, IInlineConstraintResolver constraintResolver) {
return CreateRouteEntries(GetRoutePrefixes(controllerDescriptor), factories, actionDescriptors, constraintResolver, false);
}
private IEnumerable GetRoutePrefixes(HttpControllerDescriptor controllerDescriptor) {
Collection attributes = controllerDescriptor.GetCustomAttributes (false);
if (attributes == null)
return new string[] {
null
};
var prefixes = new List ();
foreach(var attribute in attributes) {
if (attribute == null)
continue;
string prefix = attribute.Prefix;
if (prefix == null)
throw new InvalidOperationException("Prefix can not be null. Controller: " + controllerDescriptor.ControllerType.FullName);
if (prefix.EndsWith("/", StringComparison.Ordinal))
throw new InvalidOperationException("Invalid prefix" + prefix + " in " + controllerDescriptor.ControllerName);
prefixes.Add(prefix);
}
if (prefixes.Count == 0)
prefixes.Add(null);
return prefixes;
}
private IReadOnlyList CreateRouteEntries(IEnumerable prefixes, IReadOnlyCollection factories, IReadOnlyCollection actions, IInlineConstraintResolver constraintResolver, bool targetIsAction) {
var entries = new List ();
foreach(var prefix in prefixes) {
foreach(IDirectRouteFactory factory in factories) {
RouteEntry entry = CreateRouteEntry(prefix, factory, actions, constraintResolver, targetIsAction);
entries.Add(entry);
}
}
return entries;
}
private static RouteEntry CreateRouteEntry(string prefix, IDirectRouteFactory factory, IReadOnlyCollection actions, IInlineConstraintResolver constraintResolver, bool targetIsAction) {
DirectRouteFactoryContext context = new DirectRouteFactoryContext(prefix, actions, constraintResolver, targetIsAction);
RouteEntry entry = factory.CreateRoute(context);
ValidateRouteEntry(entry);
return entry;
}
private static void ValidateRouteEntry(RouteEntry routeEntry) {
if (routeEntry == null)
throw new ArgumentNullException("routeEntry");
var route = routeEntry.Route;
if (route.Handler != null)
throw new InvalidOperationException("Direct route handler is not supported");
}
}
}
3bTechTalkRoutePrefix.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Http;
namespace _3bTechTalk.MultipleRoutePrefixAttributes
{
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = true)]
public class _3bTechTalkRoutePrefix : RoutePrefixAttribute
{
public int Order { get; set; }
public _3bTechTalkRoutePrefix(string prefix) : this(prefix, 0) { }
public _3bTechTalkRoutePrefix(string prefix, int order) : base(prefix)
{
Order = order;
}
}
}
Once done, open WebApiConfig.cs and add this below given line
config.MapHttpAttributeRoutes(new _3bTechTalkMultiplePrefixDirectRouteProvider());
That's it, now you can add multiple route prefix in your controller. Example below
[_3bTechTalkRoutePrefix("api/Car", Order = 1)]
[_3bTechTalkRoutePrefix("{CountryCode}/api/Car", Order = 2)]
public class CarController: ApiController {
[Route("Get")]
public IHttpActionResult Get() {
return Ok(new {
Id = 1, Name = "Honda Accord"
});
}
}
I have uploaded a working solution here
Happy Coding :)
You're correct that you can't have multiple route prefixes, which means solving this particular use case is not going to be straight forward. About the best way I can think of to achieve what you want with the minimal amount of modifications to your project is to subclass your controller. For example:
[RoutePrefix("bikes")]
public class BikeController : Controller
{
...
}
[RoutePrefix("{country}/bikes")]
public class CountryBikeController : BikeController
{
}
You subclassed controller will inherit all the actions from BikeController, so you don't need to redefine anything, per se. However, when it comes to generating URLs and getting them to go to the right place, you'll either need to be explicit with the controller name:
#Url.Action("Index", "CountryBike", new { country = "us" }
Or, if you're using named routes, you'll have to override your actions in your subclassed controller so you can apply new route names:
[Route("", Name = "CountryBikeIndex")]
public override ActionResult Index()
{
base.Index();
}
Also, bear in mind, that when using parameters in route prefixes, all of your actions in that controller should take the parameter:
public ActionResult Index(string country = "us")
{
...
You could use attribute routes with two ordered options.
public partial class GlossaryController : Controller {
[Route("~/glossary", Order = 2)]
[Route("~/{countryCode}/glossary", Order = 1)]
public virtual ActionResult Index()
{
return View();
}
}
If you're planning to have region specific routes for all your pages you could add a route to the route config above the default. This will work only for views/controllers without attribute routes.
routes.MapRoute(
name: "Region",
url: "{countryCode}/{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional },
constraints: new { countryCode = #"\w{2}" }
);
The best solution I've come across is detailed by NightOwl888 in response to the following question: ASP.NET MVC 5 culture in route and url. The code below is my trimmed down version of his post. It's working for me in MVC5.
Decorate each controller with a single RoutePrefix, without a culture segment. When the application starts up, the custom MapLocalizedMvcAttributeRoutes method adds a localized route entry for each controller action.
public class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
// Omitted for brevity
MapLocalizedMvcAttributeRoutes(routes, "{culture}/", new { culture = "[a-z]{2}-[A-Z]{2}" });
}
static void MapLocalizedMvcAttributeRoutes(RouteCollection routes, string urlPrefix, object constraints)
{
var routeCollectionRouteType = Type.GetType("System.Web.Mvc.Routing.RouteCollectionRoute, System.Web.Mvc");
var subRouteCollectionType = Type.GetType("System.Web.Mvc.Routing.SubRouteCollection, System.Web.Mvc");
var linkGenerationRouteType = Type.GetType("System.Web.Mvc.Routing.LinkGenerationRoute, System.Web.Mvc");
FieldInfo subRoutesInfo = routeCollectionRouteType.GetField("_subRoutes", BindingFlags.NonPublic | BindingFlags.Instance);
PropertyInfo entriesInfo = subRouteCollectionType.GetProperty("Entries");
MethodInfo addMethodInfo = subRouteCollectionType.GetMethod("Add");
var localizedRouteTable = new RouteCollection();
var subRoutes = Activator.CreateInstance(subRouteCollectionType);
Func<Route, RouteBase> createLinkGenerationRoute = (Route route) => (RouteBase)Activator.CreateInstance(linkGenerationRouteType, route);
localizedRouteTable.MapMvcAttributeRoutes();
foreach (var routeCollectionRoute in localizedRouteTable.Where(rb => rb.GetType().Equals(routeCollectionRouteType)))
{
// routeCollectionRoute._subRoutes.Entries
foreach (RouteEntry routeEntry in (IEnumerable)entriesInfo.GetValue(subRoutesInfo.GetValue(routeCollectionRoute)))
{
var localizedRoute = CreateLocalizedRoute(routeEntry.Route, urlPrefix, constraints);
var localizedRouteEntry = new RouteEntry(string.IsNullOrEmpty(routeEntry.Name) ? null : $"{routeEntry.Name}_Localized", localizedRoute);
// Add localized and default routes and subroute entries
addMethodInfo.Invoke(subRoutes, new[] { localizedRouteEntry });
addMethodInfo.Invoke(subRoutes, new[] { routeEntry });
routes.Add(createLinkGenerationRoute(localizedRoute));
routes.Add(createLinkGenerationRoute(routeEntry.Route));
}
}
var routeEntries = Activator.CreateInstance(routeCollectionRouteType, subRoutes);
routes.Add((RouteBase)routeEntries);
}
static Route CreateLocalizedRoute(Route route, string urlPrefix, object constraints)
{
var routeUrl = urlPrefix + route.Url;
var routeConstraints = new RouteValueDictionary(constraints);
// combine with any existing constraints
foreach (var constraint in route.Constraints)
{
routeConstraints.Add(constraint.Key, constraint.Value);
}
return new Route(routeUrl, route.Defaults, routeConstraints, route.DataTokens, route.RouteHandler);
}
}
I was looking for an implementation / example of loading and authorizing a resource at a controller level. I am looking for the same functionality as load_and_authorize_resource in the cancan gem in ruby on rails.
Has anyone come across one / have an example how to implement something similar using Mvc .Net attributes?
Thanks!
The load_and_authorize_resource behaviour
With rails, controller and model names are linked up by convention. The attribute load_and_authorize_resource takes that to its advantage. When an action is hit that requires an instance of a resource, the load_and_authorize_resource verifies whether the instance of the resource can be accessed. If it can, it will load it up in an instance variable, if it cant, it will return a 404 or any error behaviour you have configured the attribute to produce.
For example, if I have a resource picture, and only user that own a certain picture can edit the picture's name.
So we would have a Edit action, which obviously would have a pictureId of the picture you want to edit. load_and_authorize_resource would verify whether the current context/user has access to the resource.
Here is a small video introduction of the module.
I am not aware of the existence of such plugin for ASP.NET MVC. To mimic it's functionality you could write a custom Authorize attribute though:
public class LoadAndAuthorizeResourceAttribute : AuthorizeAttribute
{
private class ModelDescriptor
{
public string Name { get; set; }
public Type ModelType { get; set; }
}
private const string ModelTypeKey = "__ModelTypeKey__";
public override void OnAuthorization(AuthorizationContext filterContext)
{
var parameters = filterContext.ActionDescriptor.GetParameters();
if (parameters.Length > 0)
{
// store the type of the action parameter so that we could access it later
// in the AuthorizeCore method
filterContext.HttpContext.Items[ModelTypeKey] = new ModelDescriptor
{
Name = parameters[0].ParameterName,
ModelType = parameters[0].ParameterType,
};
}
base.OnAuthorization(filterContext);
}
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
var authorized = base.AuthorizeCore(httpContext);
if (!authorized)
{
// the user is not authenticated or authorized => no need to continue
return false;
}
// get the currently authenticated username
string username = httpContext.User.Identity.Name;
// get the id of the resource that he is trying to manipulate
// the id should be sent either as part of the query string or the routes
string id = httpContext.Request.RequestContext.RouteData.Values["id"] as string;
// get the action param type
var modelDescriptor = httpContext.Items[ModelTypeKey] as ModelDescriptor;
if (modelDescriptor == null)
{
throw new InvalidOperationException("The controller action that was decorated with this attribute must take a model as argument");
}
// now load the corresponding entity from your database given the
// username, id and type
object model = LoadModel(id, username, modelDescriptor.ModelType);
if (model == null)
{
// the model that satisfies the given criteria was not found in the database
return false;
}
httpContext.Request.RequestContext.RouteData.Values[modelDescriptor.Name] = model;
return true;
}
private object LoadModel(string id, string username, Type modelType)
{
// TODO: depending on how you are querying your database
// you should load the corresponding model here or return null
// if not found
throw new NotImplementedException();
}
}
and now you could have a controller action that is decorated with this attribute:
[LoadAndAuthorizeResource]
public ActionResult Edit(Picture model)
{
... if we get that far the user is authorized to modify this model
}
In my controller I have the following code:
var viewModel = new ListCityViewModel {
City = rowData,
Meta =
{
DataSourceID = dataSourceID,
Em0 = em0
}
};
In my viewModel I have the following:
public class ListCityViewModel : BaseViewModel
{
public ListCitiesViewModel()
{
Meta = new Meta
{
Title = ViewContext.Controller.ValueProvider.GetValue("controller").RawValue +
ViewContext.Controller.ValueProvider.GetValue("action").RawValue,
Desc = ViewContext.Controller.ValueProvider.GetValue("controller").RawValue +
ViewContext.Controller.ValueProvider.GetValue("action").RawValue
};
}
public ICollection<City> Cities { get; set; }
}
and:
public class BaseViewModel
{
public BaseViewModel()
{
}
public Meta Meta { get; set; }
}
However it's not working as I get a message:
Error 6 An object reference is required for the non-static field,
method, or property 'System.Web.Mvc.ControllerContext.Controller.get'
Can anyone help me with this one. Do I need to pass something to the viewModel from the controller and how can I pass it. I have this viewModel common to many actions so I would like this to be automatic rather than me having to specify in the controller the controller name and action name.
In short : do not do that. It is not the right thing to do in MVC pattern. Your viewmodels should be as dumb as possible, and without any "contexts". If you need some "meta" data in your view models, depending for example on route data (action, controller), write a custom filter, which will put it there in OnActionExecuted - for example look by reflection if the current viewmodel has your "meta" properties (by this you make your own convention) and fill them from route data.
Is there a way to set a friendly name on FormsAuthentication so I can have access to both the ID and friendly name in the Context.User.Identity
I'd Like to display the First/Last name with a url pointing to the profile page of the user by the userid.
This is what I currently have:
View
#var user = Context.User.Identity;
#if (Request.IsAuthenticated)
{
#Html.ActionLink(user.Name, "Details", "Users", new { id = user.Name });
}
As you can see, it will show only the UserId.
Controller
User user = authService.ValidateUser(model.Email, model.Password);
string alias = string.Format("{0} {1}", user.FirstName, user.LastName);
//I'd like some way to set both the user.UserId and alias in the cookie and access it in the view
FormsAuthentication.SetAuthCookie(user.UserId, createPersistentCookie);
Yes, just create your own implementation of IPrincipal. Then hook up an HttpModule to the PostAuthenticated event where you instantiate your principal object and set CurrentUser to that instance. Now anytime you access CurrentUser you will get your instance of the IPrincipal that is decorated with all of the extra data you need.
I think the best way to do this is to use a common view model that has this property. Have all of your other view models derive from this model. Use a base controller and override the OnActionExecuted method, setting the common view model properties when the result being returned is a ViewResult. Your views would be strongly typed either to the common view model or a subclass from it, allowing you to reference the properties directly.
public class CommonViewModel
{
public string UserDisplayName { get; set; }
public string Username { get; set; }
}
public class FooViewModel : CommonViewModel
{
// view-specific properties
}
public class BaseController : Controller
{
public override void OnActionExecuted( ActionExecutedContext context )
{
if (context.Result is ViewResult)
{
UpdateCommonModel( ((ViewResult)context.Result).ViewData.Model as CommonViewModel );
}
}
private void UpdateCommonModel( CommonViewModel model )
{
User user = authService.ValidateUser(model.Email, model.Password);
modelUserDisplayName = string.Format("{0} {1}", user.FirstName, user.LastName);
model.Username = user.Name;
}
}
View
#if (Request.IsAuthenticated)
{
#Html.ActionLink(model.UserDisplayName, "Details", "Users", new { id = Model.Username });
}
If you're using MVC 3 you can use a global filter instead if you really did not want to add a base controller.
Here is a good article on this topic, and it has been written in regards of MVC 4:
http://www.codeproject.com/Tips/574576/How-to-implement-a-custom-IPrincipal-in-ASP-NET-MV
There are two little mistakes in the code, but I have pointed them out in a comment at the bottom of the article.