WebApi 2 routing without MVC route config isn't working - asp.net-mvc

TL;TD I've created a new WebApi2 Application and removed all the default MVC guff so just WebApi guff remains. Why isn't it working.
I've created a Web Api 2 project and don't need any non Web Api functionality so I removed it prior to creating my WebApi route and controller. No matter how I try to access it, I cant hit my new web api controller action. Code snippets below;
Global.asax
public class WebApiApplication : HttpApplication
{
protected void Application_Start()
{
GlobalConfiguration.Configure(WebApiConfig.Register);
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
}
}
WebApiConfig.cs
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "{controller}/{action}/{id}"
);
}
}
TestController.cs
public class TestController : ApiController
{
public IEnumerable<TestItem> Get()
{
var updates = new List<TestItem>()
{
new TestItem()
{
Title = "Testing Testing",
Content = "Testing Content",
Date = DateTime.Now
}
};
return updates;
}
}
Project Structure
App_Start
FilterConfig.cs
WebApiConfig.cs
Controllers
TestController.cs
Models
TestItem.cs
Global.asax
I am completely at a loss, I'm sure I've missed something obvious.

Your route is defined as the following:
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "{controller}/{action}/{id}"
);
This is expecting a route which is in 3 segments; i.e. http://localhost/Test/Get/1. However, you don't have any action which matches this. The only action you have matches http://localhost/Test/Get/.
You could correct this by adding defaults: new { id = RouteParameter.Optional } to your Http Route. However, I highly encourage you to consider switching to Attribute based Routing instead. With Attribute routing, you use attributes in your controller to manage your routes, rather than using a magic string routing table. For Example:
[RoutePrefix("Test")]
public class TestController : ApiController {
// http://localhost/Test/Get
[Route("Get")]
public IEnumerable<TestItem> Get() { ...
}
//http://localhost/Test/Get/1
[Route("Get/{id}")
public TestItem Get(int id) { ...
}
}

It's not possible to see what is causing your WebApi to fail from the supplied code, but this will give you a working WebApi with minimal setup.
A side note, FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters) is for MVC filters and not used by WebApi. You should instead use config.Filters.Add(new SomeFilter()) in your WebApiConfig.csfile.
Make a GET request to http://localhost:80/api/test (or whatever port it is running on) and it will return a list of TestItem in either XML or JSON depending on your clients http headers.
Global.asax.cs
public class WebApiApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
GlobalConfiguration.Configure(WebApiConfig.Register);
}
}
WebApiConfig.cs
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
}
TestController.cs
public class TestController : ApiController
{
public IEnumerable<TestItem> Get()
{
var updates = new List<TestItem>()
{
new TestItem()
{
Title = "Testing Testing",
Content = "Testing Content",
Date = DateTime.Now
}
};
return updates;
}
}
TestItem.cs
public class TestItem
{
public TestItem()
{
}
public string Content { get; set; }
public DateTime Date { get; set; }
public string Title { get; set; }
}
I have the following nuget packages installed:
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Microsoft.AspNet.WebApi" version="5.2.3" targetFramework="net452" />
<package id="Microsoft.AspNet.WebApi.Client" version="5.2.3" targetFramework="net452" />
<package id="Microsoft.AspNet.WebApi.Core" version="5.2.3" targetFramework="net452" />
<package id="Microsoft.AspNet.WebApi.WebHost" version="5.2.3" targetFramework="net452" />
</packages>

Related

Azure App Configuration Service Display change in MVC app .NET Core 3.x without Refreshing the Page

What I am trying to do: I have setup Azure App Configuration with a .net core 3.1 mvc web application with a sentinel key in Azure App Configuration, with the goal of as and when i update the values of different keys along with the their sentinel keys the updated value should get reflected in my mvc App without refreshing the Page.
What my issue is: When I do this using the RefreshAll: true inside the option dependency Injection in my Program.cs class I can view the changes in my app after refreshing the page, but I want to see the changes as soon as i update the key value in my App Configuration Service (without refreshing the page)
Documentation I am referencing: https://learn.microsoft.com/en-us/azure/azure-app-configuration/enable-dynamic-configuration-aspnet-core?tabs=core3x#reload-data-from-app-configuration I have created the app using the above link only.
My Environment: Using dot net core 3.1 being run from Visual Studio Enterprise 2019
My code :
Programe.cs --
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
webBuilder.ConfigureAppConfiguration((hostingContext, config) =>
{
var settings = config.Build();
config.AddAzureAppConfiguration(options =>
{
options.Connect(settings["ConnectionStrings:AppConfig"])
.ConfigureRefresh(refresh =>
{
refresh.Register("TestApp:Settings:Sentinel", refreshAll: true)
.SetCacheExpiration(new TimeSpan(0, 0, 1));
});
});
})
.UseStartup<Startup>());
Added a Setting.cs class :
namespace TestAppConfig
{
public class Settings
{
public string BackgroundColor { get; set; }
public long FontSize { get; set; }
public string FontColor { get; set; }
public string Message { get; set; }
}
}
in the Startup.cs Modified the ConfigureService Method :
public void ConfigureServices(IServiceCollection services)
{
services.Configure<Settings>(Configuration.GetSection("TestApp:Settings"));
services.AddControllersWithViews();
}
Also Updated the Configure Method as :
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
// Add the following line:
app.UseAzureAppConfiguration();
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
});
}
My HomeController Class :
public class HomeController : Controller
{
private readonly Settings _settings;
private readonly ILogger<HomeController> _logger;
public HomeController(ILogger<HomeController> logger, IOptionsSnapshot<Settings> settings)
{
_logger = logger;
_settings = settings.Value;
}
public IActionResult Index()
{
ViewData["BackgroundColor"] = _settings.BackgroundColor;
ViewData["FontSize"] = _settings.FontSize;
ViewData["FontColor"] = _settings.FontColor;
ViewData["Message"] = _settings.Message;
return View();
}
// ...
}
Index.cshtml :
<!DOCTYPE html>
<html lang="en">
<style>
body {
background-color: #ViewData["BackgroundColor"]
}
h1 {
color: #ViewData["FontColor"];
font-size: #ViewData["FontSize"]px;
}
</style>
<head>
<title>Index View</title>
</head>
<body>
<h1>#ViewData["Message"]</h1>
</body>
</html>
Let me know if this can be achieved and how .
Thanks in Advance
In the real world, when the configuration is changed, new users to your web application will see the change. If you want to see the change without refreshing the page, you will have to use the javascript or AJAX to refresh the page automatically, for example, on a timer. This is no different when anything is changed on the server-side regardless of whether it's the configuration or something else.

Using a controller with WEB API Routing

In my project (Asp.net MVC), I want to use DevExtreme GridView to display my data. I've used code first to create databases and tables. In the project, I have a model with the name of Member. I did right click on the Controller folder and select Add->Controller->DevExtreme Web API Controller with actions, using Entity Framework. In the wizard, I selected my database context and model and determine my controller name (MembersController) and then clicked Add. So in the Views folder, I created a folder with name Members and inside it, I added a view with name Index. (I don't know what exactly name must be for view, you suppose Index). In the index view, I used the wizard to add a DevExtreme GridView (Right-click on the view context and click on Insert A DevExtreme Control Here. In the wizard, I selected GridView as control and DatabaseContext, Member model and Members controller. You can see all of my codes in the below:
Member Mode:
Model:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace WebApplication2.Models
{
public class Member
{
#region Ctor
public Member()
{
}
#endregion
#region Properties
[Key]
public int MemberID { get; set; }
[Required(ErrorMessage ="*")]
public string FirstName { get; set; }
[Required(ErrorMessage = "*")]
public string LastName { get; set; }
public string Phone { get; set; }
public string Mobile { get; set; }
[Required(ErrorMessage = "*")]
public string NID { get; set; }
[Required(ErrorMessage = "*")]
public string MID { get; set; }
[Required(ErrorMessage = "*")]
public string SalaryID { get; set; }
#endregion
}
}
Controller:
[Route("api/Members/{action}", Name = "MembersApi")]
public class MembersController : ApiController
{
private ApplicationDbContext _context = new ApplicationDbContext();
[HttpGet]
public HttpResponseMessage Get(DataSourceLoadOptions loadOptions) {
var members = _context.Members.Select(i => new {
i.MemberID,
i.FirstName,
i.LastName,
i.Phone,
i.Mobile,
i.NID,
i.MID,
i.SalaryID
});
return Request.CreateResponse(DataSourceLoader.Load(members, loadOptions));
}
[HttpPost]
public HttpResponseMessage Post(FormDataCollection form) {
var model = new Member();
var values = JsonConvert.DeserializeObject<IDictionary>(form.Get("values"));
PopulateModel(model, values);
Validate(model);
if (!ModelState.IsValid)
return Request.CreateErrorResponse(HttpStatusCode.BadRequest, GetFullErrorMessage(ModelState));
var result = _context.Members.Add(model);
_context.SaveChanges();
return Request.CreateResponse(HttpStatusCode.Created, result.MemberID);
}
[HttpPut]
public HttpResponseMessage Put(FormDataCollection form) {
var key = Convert.ToInt32(form.Get("key"));
var model = _context.Members.FirstOrDefault(item => item.MemberID == key);
if(model == null)
return Request.CreateResponse(HttpStatusCode.Conflict, "Member not found");
var values = JsonConvert.DeserializeObject<IDictionary>(form.Get("values"));
PopulateModel(model, values);
Validate(model);
if (!ModelState.IsValid)
return Request.CreateErrorResponse(HttpStatusCode.BadRequest, GetFullErrorMessage(ModelState));
_context.SaveChanges();
return Request.CreateResponse(HttpStatusCode.OK);
}
[HttpDelete]
public void Delete(FormDataCollection form) {
var key = Convert.ToInt32(form.Get("key"));
var model = _context.Members.FirstOrDefault(item => item.MemberID == key);
_context.Members.Remove(model);
_context.SaveChanges();
}
private void PopulateModel(Member model, IDictionary values) {
string MEMBER_ID = nameof(Member.MemberID);
string FIRST_NAME = nameof(Member.FirstName);
string LAST_NAME = nameof(Member.LastName);
string PHONE = nameof(Member.Phone);
string MOBILE = nameof(Member.Mobile);
string NID = nameof(Member.NID);
string MID = nameof(Member.MID);
string SALARY_ID = nameof(Member.SalaryID);
if(values.Contains(MEMBER_ID)) {
model.MemberID = Convert.ToInt32(values[MEMBER_ID]);
}
if(values.Contains(FIRST_NAME)) {
model.FirstName = Convert.ToString(values[FIRST_NAME]);
}
if(values.Contains(LAST_NAME)) {
model.LastName = Convert.ToString(values[LAST_NAME]);
}
if(values.Contains(PHONE)) {
model.Phone = Convert.ToString(values[PHONE]);
}
if(values.Contains(MOBILE)) {
model.Mobile = Convert.ToString(values[MOBILE]);
}
if(values.Contains(NID)) {
model.NID = Convert.ToString(values[NID]);
}
if(values.Contains(MID)) {
model.MID = Convert.ToString(values[MID]);
}
if(values.Contains(SALARY_ID)) {
model.SalaryID = Convert.ToString(values[SALARY_ID]);
}
}
private string GetFullErrorMessage(ModelStateDictionary modelState) {
var messages = new List<string>();
foreach(var entry in modelState) {
foreach(var error in entry.Value.Errors)
messages.Add(error.ErrorMessage);
}
return String.Join(" ", messages);
}
protected override void Dispose(bool disposing) {
if (disposing) {
_context.Dispose();
}
base.Dispose(disposing);
}
}
View:
#{
Layout = "~/Views/Shared/_Layout.cshtml";
}
#(Html.DevExtreme().DataGrid<WebApplication2.Models.Member>()
.DataSource(ds => ds.WebApi()
.RouteName("MembersApi")
.LoadAction("Get")
.InsertAction("Post")
.UpdateAction("Put")
.DeleteAction("Delete")
.Key("MemberID")
)
.RemoteOperations(true)
.Columns(columns => {
columns.AddFor(m => m.MemberID);
columns.AddFor(m => m.FirstName);
columns.AddFor(m => m.LastName);
columns.AddFor(m => m.Phone);
columns.AddFor(m => m.Mobile);
columns.AddFor(m => m.NID);
columns.AddFor(m => m.MID);
columns.AddFor(m => m.SalaryID);
})
.Editing(e => e
.AllowAdding(true)
.AllowUpdating(true)
.AllowDeleting(true)
)
)
WebApiConfig.cs file:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web.Http;
namespace WebApplication2
{
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
// WebAPI when dealing with JSON & JavaScript!
// Setup json serialization to serialize classes to camel (std. Json format)
var formatter = GlobalConfiguration.Configuration.Formatters.JsonFormatter;
formatter.SerializerSettings.ContractResolver =
new Newtonsoft.Json.Serialization.CamelCasePropertyNamesContractResolver();
}
}
}
Global.asax.cs file:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Http;
using System.Web.Mvc;
using System.Web.Optimization;
using System.Web.Routing;
namespace WebApplication2
{
public class MvcApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
GlobalConfiguration.Configure(WebApiConfig.Register);
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
}
}
}
In addition I've installed all requirements for this project according this link.
But when I try to show View with https://localhost:44328/Members/index RUL, I get this error:
The resource cannot be found.
Description: HTTP 404. The resource you are looking for (or one of its dependencies) could have been removed, had its name changed, or is temporarily unavailable. Please review the following URL and make sure that it is spelled correctly.
Requested URL: /Members/index
I'v tried a lot way to correct my wrong but I couldn't find solution. I almost read all of documents about routing (mvc and web api), but after about 5 days I still couldn't to solve it.
Thanks a lot for answer me.
The thing is as far as I can tell, one of the reasons you are receiving a 404 is because you don't seem to be adding your parameter anywhere. Aside from that your 'DataSourceLoadOptions loadOptions' shouldn't be used as a parameter because it is probably too complex. Shouldn't you create a service which retrieves your loadOptions instead of you giving it along?
If you want all members without giving information then you should do exactly that. Not give the request some metadata it doesn't know about along for the ride.
I suggest you do the following:
Create an API which does not need metadata like how to get a datasource. Things such as Members.LastName are acceptable
Make sure you create a service which is responsible for getting your data in the first place. This means also removing all that extra code in your controller and placing it in a more suitable location.
Keep your classes clean and simple. Your controller now has too many responsibilities.
Hopefully this'll help. If you try your API GET Method as is without the 'DataSourceLoadOptions loadOptions' parameter, then your API will not return 404.
Since you didn't put in your ajax call url, I'm going to have to work with this
Requested URL: /Members/index
This is a problem, your webApi default route requires your URL to be prepended with /api/
So something like this should work /api/Members, so you can remove the Index part of that URL as the request type will handle which Action is executed ie HTTPGet/HTTPPost
EDIT: Use this as your route
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { controller = "Members" id = RouteParameter.Optional }
);

How to use asp.net web api different method in same controller?

I want to use to asp.net web api different method in same api controller. I searched but I couldn't find.
For example:
public class HomeController : ApiController
{
AdFindDBEntities db = new AdFindDBEntities();
public HomeController()
{
db.Configuration.ProxyCreationEnabled = false;
}
[HttpGet]
public List<Ad> AllAds()
{
return db.Ad.ToList();
}
[HttpGet]
public IEnumerable<Ad> GetLastAds()
{
return db.Ad.OrderByDescending(x => x.CreatedDate).Take(20).ToList();
}
}
When I run the project AllAds method running. I don't know how use to GetLastAds method. Please help me!
Use [Route] attribute for separation call you actions
[HttpGet]
[Route("api/home/ads}")]
public List<Ad> AllAds()
{
...
}
[HttpGet]
[Route("api/home/ads/last}")]
public List<Ad> GetLastAds()
{
...
}
change the default route to have {action} in the route.
routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional }
In that way you can call multiple actions by different urls.

How to share Session variables between controllers action in MVC 3?

I have a gridPanel in the first view. If i click a button in this view, the selected rows will be displayed in another view. I need to send information with these rows to the server so i can load some extra data in the action proper to the click button.
To perform that, i should use Session instead of TempData or ViewBag/ViewData because i don't know when the user will click the button. Please correct me if i'm mistaken.
My code is like that: In the client side with AJAX i call an action method to set the session variables:
Ext.Ajax.request({ url: 'Examples/SetSelectedStations', params: { selectedStations: stationsStr} });
in the SetSelectedStations controller i set the Session["selected"] so the controller action lokks like:
public ViewResult SetSelectedStations(string selectedStations)
{
Session["selected"] = selectedStations;
return View();
}
and i want to get Session["selected"] in an other controller called ShowSelectedStations:
public Ext.Net.MVC.PartialViewResult ShowSelectedStations(string containerId)
{
string ss = Session["selected"] as string;
// Here ss is null !!!
}
The problem is Session["selected"] is always null in the second controller!!!
Should i define Session["selected"] in other place? Is there a special configuration in the web.confg file?
Please notice that the sessionState in my web.config is like that:
<sessionState mode="Custom" customProvider="PgSessionStateStoreProvider">
<providers>
<clear />
<add name="PgSessionStateStoreProvider" type="NauckIT.PostgreSQLProvider.PgSessionStateStoreProvider" enableExpiredSessionAutoDeletion="true" expiredSessionAutoDeletionInterval="1800000" enableSessionExpireCallback="false" connectionStringName="AspSQLProvider" applicationName="WebSite1" />
</providers>
</sessionState>
and the global.asax:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Web.Routing;
namespace WIS_3_0
{
// Note: For instructions on enabling IIS6 or IIS7 classic mode,
// visit http://go.microsoft.com/?LinkId=9394801
public class MvcApplication : System.Web.HttpApplication
{
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new HandleErrorAttribute());
}
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Examples", action = "Ex3", id = UrlParameter.Optional } // Parameter defaults
);
}
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
RegisterGlobalFilters(GlobalFilters.Filters);
RegisterRoutes(RouteTable.Routes);
}
}
}

Redirect when the user uses IE

I created this Custom Route Class in ASP.NET MVC:
public class UserAgentConstraint:IRouteConstraint {
private string RequiredUserAgent;
public UserAgentConstraint(string agentParam) {
RequiredUserAgent = agentParam;
}
public bool Match(HttpContextBase httpContext, Route route, string parameterName,
RouteValueDictionary values, RouteDirection routeDirection) {
return httpContext.Request.UserAgent != null && !httpContext.Request.UserAgent.Contains(RequiredUserAgent);
}
}
And in Global.asax.cs:
public static void RegisterRoutes(RouteCollection routes) {
routes.MapRoute("myRoute2", "{controller}/{action}/{Id}",
new { controller = "home", action = "index", Id = UrlParameter.Optional }, new {
customConstriant=new UserAgentConstraint("IE")
}
}
The above code works prefectly, but when the user uses IE, I get a 404 Error. I want to redirect to a custom Page. I dont want to use a Custom Error in the Web.Config file because my error is only for use in IE. How can one do this?
Thanks in your advice.
a better way of doing this is using ActionFilter.
public class BrowserFilterAttribute : ActionFilterAttribute
{
public string [] _browserNames { get; set; }
public AssetIdFilterAttribute(params string [] browserNames)
{
_browserNames= browserNames;
}
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
//get browser name from somewhere
string currentBrowser = filterContext.HttpContext.Request.Browser.Browser;
if(_browserNames.Contains(currentBrowser))
filterContext.Result = new RedirectResult("your URL");
}
}
you can apply it in Controller level like this :
[BrowserFilter("IE","Opera","SomeOtherBrowser")]
public class BrowserAwareController() : Controller
{
}
hope this help.good luck.

Resources